# dotbatch

**Atomic batch operations on nested data structures**

Part of the Shape pillar, `dotbatch` orchestrates multiple `dotmod` operations as a single atomic unit. All operations succeed together or none are applied.

## Overview

`dotbatch` collects a sequence of modification operations and applies them in one pass. It wraps `dotmod`'s `set_`, `delete_`, `update_`, and `append_` functions, applying them sequentially to a deep copy of the original data.

## Core API

### Batch Class

The `Batch` class provides a fluent, chainable interface for building a transaction of operations.

```python
from shape.dotbatch import Batch

data = {
    "account": {
        "balance": 100,
        "transactions": []
    }
}

# Create a batch of operations
batch = Batch(data)
batch.set("account.balance", 50)
batch.append("account.transactions", {"amount": -50, "type": "withdrawal"})
batch.set("account.last_modified", "2024-01-01")

# Apply all at once
result = batch.apply()
# Original data is unchanged
```

### Batch Methods

All mutation methods return `self` for chaining:

```python
batch = Batch(data)

# Set values
batch.set("user.name", "Alice")
batch.set("user.email", "alice@example.com")

# Merge a dict into a dict at a path
batch.update("user.settings", {"theme": "dark", "font_size": 14})

# Delete paths
batch.delete("temp.data")
batch.delete("user.old_field")

# Append to lists
batch.append("logs", {"timestamp": "2024-01-01", "action": "login"})

# Apply all -- returns new data, original unchanged
result = batch.apply()
```

Methods can also be chained fluently:

```python
result = (
    Batch(data)
    .set("user.name", "Alice")
    .set("user.email", "alice@example.com")
    .delete("user.temp_token")
    .apply()
)
```

### Operation Data Class

Each operation is represented as a frozen `Operation` dataclass:

```python
from shape.dotbatch.core import Operation

op = Operation(verb="set", path="user.name", value="Alice")
# verb: "set" | "delete" | "update" | "append"
# path: dot-notation path string
# value: the value to apply (optional for "delete")
```

Operations can also be created from dictionaries:

```python
op = Operation.from_dict({"verb": "set", "path": "user.name", "value": "Alice"})
```

### apply() Function

A convenience function applies a declarative list of operation dictionaries in one call:

```python
from shape.dotbatch import apply

data = {"counter": 0, "tags": ["a"]}

operations = [
    {"verb": "set", "path": "counter", "value": 1},
    {"verb": "append", "path": "tags", "value": "b"},
    {"verb": "delete", "path": "temp"}
]

result = apply(data, operations)
# {"counter": 1, "tags": ["a", "b"]}
```

## Real-World Examples

### Bank Transfer

```python
from shape.dotbatch import Batch
from depth.dotget import get

def transfer_funds(accounts, from_id, to_id, amount):
    """Transfer funds between accounts atomically."""
    from_balance = get(accounts, f"{from_id}.balance")
    if from_balance < amount:
        raise ValueError("Insufficient funds")

    to_balance = get(accounts, f"{to_id}.balance")

    result = (
        Batch(accounts)
        .set(f"{from_id}.balance", from_balance - amount)
        .append(f"{from_id}.transactions", {
            "type": "debit", "amount": -amount, "to": to_id
        })
        .set(f"{to_id}.balance", to_balance + amount)
        .append(f"{to_id}.transactions", {
            "type": "credit", "amount": amount, "from": from_id
        })
        .apply()
    )
    return result
```

### Configuration Update

```python
from shape.dotbatch import apply

changes = [
    {"verb": "set", "path": "server.host", "value": "api.example.com"},
    {"verb": "set", "path": "server.port", "value": 443},
    {"verb": "set", "path": "server.ssl", "value": True},
    {"verb": "delete", "path": "server.debug"}
]

prod_config = apply(config, changes)
```

## Command Line

```bash
# Apply a changeset file to a data file
dotbatch changeset.json data.json

# Read data from stdin, changeset from file
echo '{"counter": 0}' | dotbatch changeset.json

# Both files as positional arguments
dotbatch changeset.yaml data.yaml
```

The changeset file is a JSON or YAML list of operation objects:

```json
[
    {"verb": "set", "path": "user.name", "value": "Alice"},
    {"verb": "delete", "path": "user.temp_token"},
    {"verb": "append", "path": "user.tags", "value": "verified"},
    {"verb": "update", "path": "user.settings", "value": {"theme": "dark"}}
]
```

### Arguments

| Argument | Description |
|----------|-------------|
| `changeset_file` | Path to JSON/YAML file containing the list of operations, or `-` for stdin |
| `data_file` | Path to source JSON/YAML data file, or `-` for stdin (default) |

### Options

| Flag | Description |
|------|-------------|
| `--in`, `--input-format` | Input format: `json` or `yaml` (inferred from extension if omitted) |

Note: Both `changeset_file` and `data_file` cannot be `-` (stdin) at the same time.

## How It Works

1. `Batch` collects `Operation` objects into an internal list (`batch.ops`)
2. On `apply()`, a single `deepcopy` of the original data is made
3. Each operation is applied sequentially using `dotmod` functions (`set_`, `delete_`, `update_`, `append_`)
4. The final modified copy is returned; the original is never touched

## Related Tools

- **[dotmod](dotmod.md)** - Individual modifications
- **[dotpipe](dotpipe.md)** - Sequential transformations
- **[dotget](../depth/dotget.md)** - Read values for validation
