Data API
Delta Synchronization

Delta Synchronization

Delta sync enables efficient incremental data synchronization by fetching only records modified since your last sync. HTTP calls use intapp-rest-client; see Error handling for API errors.

Overview

MethodPurpose
get_modified_entries()Get IDs modified since a datetime
aget_modified_entries()Async version
sync_delta()Convenience helper for common pattern

Quick Start

from dealcloud_sdk import DealCloud, DealCloudConfig
from datetime import datetime, timedelta
 
config = DealCloudConfig(
    siteUrl="yoursite.dealcloud.com",
    clientId=12345,
    clientSecret="your-secret",
)
dc = DealCloud.from_config_object(config)
 
# Get changes from last hour
last_sync = datetime.now() - timedelta(hours=1)
result = dc.sync_delta("Company", last_sync)
 
print(f"Modified: {len(result.modified_ids)}")
print(f"Deleted: {len(result.deleted_ids)}")
 
# Process changes
for row in result.modified_data:
    your_db.upsert(row)
 
for entry_id in result.deleted_ids:
    your_db.delete(entry_id)

get_modified_entries()

Returns Entry IDs modified since a specific datetime:

from datetime import datetime, timedelta
 
# Get modified entries
last_sync = datetime.now() - timedelta(days=1)
changes = dc.get_modified_entries("Company", last_sync)
 
for change in changes:
    print(f"Entry {change.entry_id}: deleted={change.is_deleted}")

Return Value

@dataclass
class ModifiedEntry:
    entry_id: int
    modified_date: datetime
    is_deleted: bool

Parameters

ParameterTypeDescription
object_idstr | intObject API name or ID
modified_sincedatetime | strCutoff datetime
include_deletedboolInclude deleted entries (default: True)
đź’ˇ

get_modified_entries() returns ALL modified IDs in a single response - no pagination. The response is lightweight (just IDs and metadata).

sync_delta()

The sync_delta() helper implements the most common delta sync pattern:

from datetime import datetime, timedelta
 
last_sync = datetime.now() - timedelta(hours=1)
 
# All-in-one delta sync
result = dc.sync_delta(
    "Company",
    last_sync,
    fields=["CompanyName", "Revenue", "Status"],  # Optional
    output="list"  # or "pandas"
)
 
# Result contains everything you need
print(f"Sync timestamp: {result.sync_timestamp}")
print(f"Modified IDs: {result.modified_ids}")
print(f"Deleted IDs: {result.deleted_ids}")
print(f"Modified data: {len(result.modified_data)} records")

Return Value

@dataclass
class DeltaSyncResult:
    sync_timestamp: datetime      # Timestamp for next sync
    modified_ids: List[int]       # IDs that were modified
    deleted_ids: List[int]        # IDs that were deleted
    modified_data: List[dict]     # Full data for modified records

Parameters

ParameterTypeDescription
object_idstr | intObject API name or ID
modified_sincedatetime | strCutoff datetime
fieldsList[str]Fields to fetch (default: all)
outputstr"list", "pandas", "polars", or "polars_lazy"
include_deletedboolInclude deleted entries

Output Formats

from datetime import datetime, timedelta
 
last_sync = datetime.now() - timedelta(hours=1)
 
# List (default)
result = dc.sync_delta("Company", last_sync)
for row in result.modified_data:
    print(row)
 
# Pandas DataFrame
result = dc.sync_delta("Company", last_sync, output="pandas")
df = result.modified_data  # pandas.DataFrame
 
# Polars DataFrame
result = dc.sync_delta("Company", last_sync, output="polars")
df = result.modified_data  # polars.DataFrame
 
# Polars LazyFrame (for optimization)
result = dc.sync_delta("Company", last_sync, output="polars_lazy")
lf = result.modified_data  # polars.LazyFrame
df = lf.collect()  # Execute lazy operations

Building Your Own Pattern

For custom requirements, use get_modified_entries() directly:

from datetime import datetime, timedelta
 
from dealcloud_sdk import DealCloud, DealCloudConfig
 
config = DealCloudConfig(
    siteUrl="yoursite.dealcloud.com",
    clientId=12345,
    clientSecret="your-secret",
)
dc = DealCloud.from_config_object(config)
 
# 1. Get modified entry IDs
last_sync = datetime.now() - timedelta(hours=6)
changes = dc.get_modified_entries("Company", last_sync)
 
# 2. Separate modifications from deletions
modified_ids = [c.entry_id for c in changes if not c.is_deleted]
deleted_ids = [c.entry_id for c in changes if c.is_deleted]
 
print(f"Modified: {len(modified_ids)}, Deleted: {len(deleted_ids)}")
 
# 3. Fetch data for modifications
if modified_ids:
    # Choose method based on volume
    if len(modified_ids) < 500:
        # Small batch: direct query
        # Option 1: DealCloud query string format (simpler)
        ids_str = ",".join(map(str, modified_ids))
        data = dc.read_data(
            "Company",
            output="list",
            query=f"EntryId in ({ids_str})"
        )
        # Option 2: MongoDB-style format (also works)
        # data = dc.read_data(
        #     "Company",
        #     output="list",
        #     query=f"{{EntryId: {{$in: [{ids_str}]}}}}"
        # )
    else:
        # Large batch: streaming
        ids_str = ",".join(map(str, modified_ids))
        for row in dc.read_data_streaming(
            "Company",
            query=f"EntryId in ({ids_str})"
        ):
            process_row(row)
 
# 4. Handle deletions
for entry_id in deleted_ids:
    your_db.delete(entry_id)
 
# 5. Save sync timestamp
save_last_sync(datetime.now())

Async Version

import asyncio
from datetime import datetime, timedelta
 
from dealcloud_sdk import DealCloud, DealCloudConfig
 
async def delta_sync_async():
    config = DealCloudConfig(
        siteUrl="yoursite.dealcloud.com",
        clientId=12345,
        clientSecret="your-secret",
    )
    dc = DealCloud.from_config_object(config)
 
    last_sync = datetime.now() - timedelta(hours=1)
    changes = await dc.aget_modified_entries("Company", last_sync)
    
    modified_ids = [c.entry_id for c in changes if not c.is_deleted]
    
    # Process asynchronously
    ids_str = ",".join(map(str, modified_ids))
    async for row in dc.aread_data_streaming(
        "Company",
        query=f"EntryId in ({ids_str})"
    ):
        await process_async(row)
 
asyncio.run(delta_sync_async())

Complete Sync Pipeline

from dealcloud_sdk import DealCloud, DealCloudConfig
from datetime import datetime
import json
from pathlib import Path
 
config = DealCloudConfig(
    siteUrl="yoursite.dealcloud.com",
    clientId=12345,
    clientSecret="your-secret",
)
dc = DealCloud.from_config_object(config)
 
class SyncState:
    """Persist sync state between runs."""
    
    def __init__(self, filepath="sync_state.json"):
        self.filepath = Path(filepath)
    
    def get_last_sync(self, object_id: str) -> datetime:
        if self.filepath.exists():
            with open(self.filepath) as f:
                state = json.load(f)
                ts = state.get(object_id)
                if ts:
                    return datetime.fromisoformat(ts)
        return datetime.min  # First sync: get everything
    
    def save_sync(self, object_id: str, timestamp: datetime):
        state = {}
        if self.filepath.exists():
            with open(self.filepath) as f:
                state = json.load(f)
        
        state[object_id] = timestamp.isoformat()
        
        with open(self.filepath, "w") as f:
            json.dump(state, f)
 
def sync_object(object_id: str, process_record, delete_record):
    """
    Sync a DealCloud object incrementally.
    
    Args:
        object_id: Object to sync
        process_record: Function to handle modified records
        delete_record: Function to handle deletions
    """
    state = SyncState()
    last_sync = state.get_last_sync(object_id)
    
    print(f"Syncing {object_id} since {last_sync}")
    
    result = dc.sync_delta(object_id, last_sync)
    
    # Process modifications
    for record in result.modified_data:
        process_record(record)
    
    # Process deletions
    for entry_id in result.deleted_ids:
        delete_record(entry_id)
    
    # Save state
    state.save_sync(object_id, result.sync_timestamp)
    
    print(f"  Modified: {len(result.modified_ids)}")
    print(f"  Deleted: {len(result.deleted_ids)}")
 
# Usage
def upsert_to_warehouse(record):
    # Your logic here
    pass
 
def delete_from_warehouse(entry_id):
    # Your logic here
    pass
 
sync_object("Company", upsert_to_warehouse, delete_from_warehouse)
sync_object("Contact", upsert_to_warehouse, delete_from_warehouse)
sync_object("Deal", upsert_to_warehouse, delete_from_warehouse)

Performance Considerations

Small Deltas (< 500 records)

# Simple approach for small deltas
result = dc.sync_delta("Company", last_sync, output="list")
process_all(result.modified_data)

Large Deltas (> 500 records)

# Streaming for large deltas
changes = dc.get_modified_entries("Company", last_sync)
modified_ids = [c.entry_id for c in changes if not c.is_deleted]
 
if modified_ids:
    ids_str = ",".join(map(str, modified_ids))
    ids_query = f"EntryId in ({ids_str})"
    for row in dc.read_data_streaming("Company", query=ids_query):
        process_one(row)  # Memory-efficient

Full Sync vs Delta

ScenarioApproach
First syncFull read with read_data()
Regular intervalsDelta with sync_delta()
Large changesDelta with streaming
Very old last_syncConsider full sync

Related