Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b470208
Add streaming delta read/write methods to the SDK
jcatt-sf Jul 1, 2026
5837e4c
documentation fixes and write mode removal
jcatt-sf Jul 1, 2026
6df6d83
Merge pull request #123 from forcedotcom/streaming-delta-sdk-methods
jcatt-sf Jul 1, 2026
3da41b6
rely on config for name source
jcatt-sf Jul 2, 2026
2c3d285
separate streaming client
jcatt-sf Jul 7, 2026
5baa65a
streaming source from config
jcatt-sf Jul 8, 2026
f6deec0
streaming source fix
jcatt-sf Jul 8, 2026
2cb85d3
lint
jcatt-sf Jul 8, 2026
1022658
Merge pull request #124 from forcedotcom/remove-streaming-delta-sourc…
jcatt-sf Jul 8, 2026
4c00881
Support creating and deploying streaming script code packages
jcatt-sf Jul 22, 2026
5738e80
lint
jcatt-sf Jul 23, 2026
e0603af
Merge pull request #132 from forcedotcom/Support-creating-and-deployi…
jcatt-sf Jul 24, 2026
5317119
Merge pull request #133 from forcedotcom/main
sbyrne-sf Jul 28, 2026
80261cb
Add support for external-callout
diksha-sf Jul 30, 2026
10bb3d5
Fix lint errors
diksha-sf Jul 30, 2026
756fc43
Address review comments
diksha-sf Aug 3, 2026
1f2096d
Fix build error and add example
diksha-sf Aug 3, 2026
bcfa03d
Fix error on deploy
diksha-sf Aug 3, 2026
fa645b5
Version bump in mock file
diksha-sf Aug 3, 2026
3ea49fa
merge with develop
diksha-sf Aug 3, 2026
75ad623
revert change to mock server
diksha-sf Aug 3, 2026
12eabe1
Add example for script
diksha-sf Aug 7, 2026
2c9e319
Add support for timeout header
diksha-sf Aug 10, 2026
fb22a94
Merge branch 'forcedotcom:main' into main
diksha-sf Sep 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,30 @@
# Changelog

## 6.1.0

### Added

- **`StreamingClient` for BYOC streaming (delta) transforms.**

A dedicated `StreamingClient` (alongside the batch `Client`) lets an entry point process a Data Lake Object's Change Data Feed continuously instead of reading a bounded snapshot.

- `read_dlo_deltas()` / `read_dmo_deltas()` – return a streaming DataFrame over the object's change feed.
- `write_dlo_deltas(name, dataframe)` – start a streaming query that writes each micro-batch to the target DLO and return the `StreamingQuery` handle.

The shared functions (`find_file_path`, `llm_gateway_generate_text`, `einstein_predict`) are available on both `Client` and `StreamingClient`.

```python
from datacustomcode import StreamingClient

client = StreamingClient()
deltas = client.read_dlo_deltas()
transformed = deltas.withColumn("description__c", upper(col("description__c")))
query = client.write_dlo_deltas("Output__dll", transformed)
query.awaitTermination()
```

These methods run only inside the Data Cloud streaming (`DELTA_SYNC`) runtime; locally they raise `NotImplementedError`. See the `examples/streaming_deltas/entrypoint.py` example and the "Streaming (delta) transforms" section of the README.

## 6.0.0

### Breaking Changes
Expand Down
41 changes: 39 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,15 +145,22 @@ Your Python dependencies can be packaged as .py files, .zip archives (containing

## API

Your entry point script will define logic using the `Client` object which wraps data access layers.
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.

You should only need the following methods:
For a batch transform, use `Client`. You should only need the following methods:
* `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.
* `read_dlo(name)` – Read from a Data Lake Object by name
* `read_dmo(name)` – Read from a Data Model Object by name
* `write_to_dlo(name, spark_dataframe, write_mode)` – Write to a Data Model Object by name with a Spark dataframe
* `write_to_dmo(name, spark_dataframe, write_mode)` – Write to a Data Lake Object by name with a Spark dataframe

For a streaming (delta) transform, use `StreamingClient`, which exposes the streaming counterparts:
* `read_dlo_deltas()` – Read the streaming change feed (deltas) of a Data Lake Object as a streaming DataFrame.
* `read_dmo_deltas()` – Read the streaming change feed (deltas) of a Data Model Object as a streaming DataFrame.
* `write_dlo_deltas(name, spark_dataframe)` – Write a streaming DataFrame of deltas to a Data Lake Object; returns the started `StreamingQuery`

`find_file_path`, `llm_gateway_generate_text`, and `einstein_predict` are available on both clients.

For example:
```python
from datacustomcode import Client
Expand All @@ -169,6 +176,36 @@ client.write_to_dlo('output_DLO')
> [!WARNING]
> Currently we only support reading from DMOs and writing to DMOs or reading from DLOs and writing to DLOs, but they cannot mix.

### Streaming (delta) transforms

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:

```python
from pyspark.sql.functions import col, upper

from datacustomcode import StreamingClient

client = StreamingClient()

# read_dlo_deltas returns a *streaming* DataFrame over the change feed.
# The runtime resolves the single streaming source, so no name is passed.
deltas = client.read_dlo_deltas()

# Ordinary PySpark transform.
transformed = deltas.withColumn("description__c", upper(col("description__c")))

# write_dlo_deltas starts a streaming query and returns the StreamingQuery.
# The runtime owns the trigger and checkpoint location; you
# choose only the target table.
query = client.write_dlo_deltas("Output__dll", transformed)
query.awaitTermination()
```

Notes:

- 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.
- A complete runnable entry point is provided in [`examples/streaming_deltas/entrypoint.py`](src/datacustomcode/templates/script/examples/streaming_deltas/entrypoint.py).

### Bundled file resolution

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:
Expand Down
5 changes: 5 additions & 0 deletions src/datacustomcode/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"QueryAPIDataCloudReader",
"SparkEinsteinPredictions",
"SparkLLMGateway",
"StreamingClient",
"einstein_predict_col",
"llm_gateway_generate_text_col",
]
Expand All @@ -39,6 +40,10 @@ def __getattr__(name: str):
from datacustomcode.client import Client

return Client
elif name == "StreamingClient":
from datacustomcode.client import StreamingClient

return StreamingClient
elif name == "AuthType":
from datacustomcode.credentials import AuthType

Expand Down
33 changes: 29 additions & 4 deletions src/datacustomcode/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,20 +283,43 @@ def deploy(
)
@click.option(
"--use-in-feature",
default="SearchIndexChunking",
help="Feature where this function will be used (only applicable for function).",
"-u",
default=None,
help=(
"Invoke option for this package. For scripts: 'BatchTransform' "
"(default) or 'StreamingTransform'. For functions: 'SearchIndexChunking'."
),
)
def init(directory: str, code_type: str, use_in_feature: Optional[str]):
from datacustomcode.constants import (
SCRIPT_USE_IN_FEATURE_BATCH,
SCRIPT_USE_IN_FEATURE_OPTIONS,
SCRIPT_USE_IN_FEATURE_STREAMING,
)
from datacustomcode.scan import (
dc_config_json_from_file,
update_config,
write_sdk_config,
)
from datacustomcode.template import copy_function_template, copy_script_template

streaming = False
if code_type == "script":
use_in_feature = use_in_feature or SCRIPT_USE_IN_FEATURE_BATCH
if use_in_feature not in SCRIPT_USE_IN_FEATURE_OPTIONS:
click.secho(
f"Error: Invalid --use-in-feature '{use_in_feature}' for a "
f"script. Valid options: {', '.join(SCRIPT_USE_IN_FEATURE_OPTIONS)}.",
fg="red",
)
raise click.Abort()
streaming = use_in_feature == SCRIPT_USE_IN_FEATURE_STREAMING
else:
use_in_feature = use_in_feature or "SearchIndexChunking"

click.echo("Copying template to " + click.style(directory, fg="blue", bold=True))
if code_type == "script":
copy_script_template(directory)
copy_script_template(directory, streaming=streaming)
elif code_type == "function":
copy_function_template(directory, use_in_feature)
entrypoint_path = os.path.join(directory, PAYLOAD_DIR, ENTRYPOINT_FILE)
Expand All @@ -306,7 +329,9 @@ def init(directory: str, code_type: str, use_in_feature: Optional[str]):
sdk_config = {"type": code_type}
write_sdk_config(directory, sdk_config)

config_json = dc_config_json_from_file(entrypoint_path, code_type)
config_json = dc_config_json_from_file(
entrypoint_path, code_type, streaming=streaming
)
with open(config_location, "w") as f:
json.dump(config_json, f, indent=2)

Expand Down
Loading
Loading