Skip to content
21 changes: 19 additions & 2 deletions .claude/skills/operating-weaviate-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,26 @@ weaviate-cli delete collection --collection MyCollection --json
weaviate-cli delete collection --all --json
```

Key create options: `--multitenant`, `--auto_tenant_creation`, `--auto_tenant_activation`, `--shards N`, `--vectorizer <type>`, `--named_vector`, `--replication_deletion_strategy`, `--object_ttl_type`, `--object_ttl_time`, `--object_ttl_filter_expired`, `--object_ttl_property_name` (only when `object_ttl_type=property`)
Key create options: `--multitenant`, `--auto_tenant_creation`, `--auto_tenant_activation`, `--shards N`, `--vectorizer <type>`, `--named_vector`, `--replication_deletion_strategy`, `--async_replication_config key=value` (repeatable; requires `--async_enabled` and Weaviate >= v1.36.0), `--object_ttl_type`, `--object_ttl_time`, `--object_ttl_filter_expired`, `--object_ttl_property_name` (only when `object_ttl_type=property`)

Mutable fields: `--async_enabled`, `--replication_factor`, `--vector_index`, `--description`, `--training_limit`, `--auto_tenant_creation`, `--auto_tenant_activation`, `--replication_deletion_strategy`, `--object_ttl_type`, `--object_ttl_time`, `--object_ttl_filter_expired`, `--object_ttl_property_name` (only when `object_ttl_type=property`)
Mutable fields: `--async_enabled`, `--replication_factor`, `--vector_index`, `--description`, `--training_limit`, `--auto_tenant_creation`, `--auto_tenant_activation`, `--replication_deletion_strategy`, `--async_replication_config key=value` (repeatable), `--object_ttl_type`, `--object_ttl_time`, `--object_ttl_filter_expired`, `--object_ttl_property_name` (only when `object_ttl_type=property`)

#### Async Replication Config

```bash
# Create with async replication tuning (requires --async_enabled and Weaviate >= v1.36.0)
weaviate-cli create collection --collection MyCol --async_enabled \
--async_replication_config max_workers=10 \
--async_replication_config frequency=60 \
--async_replication_config propagation_concurrency=4 --json

# Update async replication config on existing collection
weaviate-cli update collection --collection MyCol \
--async_replication_config max_workers=20 \
--async_replication_config propagation_batch_size=100 --json
```

Valid keys (all integers): `max_workers`, `hashtree_height`, `frequency`, `frequency_while_propagating`, `alive_nodes_checking_frequency`, `logging_frequency`, `diff_batch_size`, `diff_per_node_timeout`, `pre_propagation_timeout`, `propagation_timeout`, `propagation_limit`, `propagation_delay`, `propagation_concurrency`, `propagation_batch_size`

#### Object TTL

Expand Down
24 changes: 23 additions & 1 deletion .claude/skills/operating-weaviate-cli/references/collections.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ weaviate-cli create collection \
- `--hfresh_search_probe` -- (hfresh only) Search probe size (default: None, uses server default)
- `--distance_metric` -- Distance metric: cosine, dot, l2-squared, hamming, manhattan (default: None, uses server default). Applies to all vector index types.
- `--rescore_limit` -- Rescore limit for quantized indexes (default: None, uses server default)
- `--async_replication_config` -- Async replication tuning as `key=value` pairs (repeatable). Valid keys: `max_workers`, `hashtree_height`, `frequency`, `frequency_while_propagating`, `alive_nodes_checking_frequency`, `logging_frequency`, `diff_batch_size`, `diff_per_node_timeout`, `pre_propagation_timeout`, `propagation_timeout`, `propagation_limit`, `propagation_delay`, `propagation_concurrency`, `propagation_batch_size`. All values must be integers. Use `reset` to revert all to server defaults. Requires `--async_enabled` on create and Weaviate >= v1.36.0.

**hfresh examples:**
```bash
Expand All @@ -66,6 +67,19 @@ weaviate-cli create collection \
--json
```

**Async replication config examples:**
```bash
# Create with custom async replication tuning
weaviate-cli create collection --collection MyCol --async_enabled \
--async_replication_config max_workers=10 \
--async_replication_config frequency=60 \
--async_replication_config propagation_concurrency=4

# Set a single parameter
weaviate-cli create collection --collection MyCol --async_enabled \
--async_replication_config propagation_batch_size=200
```

**Object TTL examples:**
```bash
# Delete objects 1 hour after creation
Expand All @@ -90,10 +104,18 @@ weaviate-cli update collection \
--json
```

Mutable fields: `--async_enabled`, `--replication_factor`, `--vector_index`, `--description`, `--training_limit`, `--auto_tenant_creation`, `--auto_tenant_activation`, `--replication_deletion_strategy`, `--object_ttl_type`, `--object_ttl_time`, `--object_ttl_filter_expired`, `--object_ttl_property_name` (only when `object_ttl_type=property`)
Mutable fields: `--async_enabled`, `--replication_factor`, `--vector_index`, `--description`, `--training_limit`, `--auto_tenant_creation`, `--auto_tenant_activation`, `--replication_deletion_strategy`, `--async_replication_config`, `--object_ttl_type`, `--object_ttl_time`, `--object_ttl_filter_expired`, `--object_ttl_property_name` (only when `object_ttl_type=property`)

**Immutable (cannot change after creation):** multitenant, vectorizer, named_vector, shards

**Async replication config examples (update):**
```bash
# Update async replication tuning on existing collection
weaviate-cli update collection --collection MyCol \
--async_replication_config max_workers=20 \
--async_replication_config propagation_batch_size=100
```

**Object TTL options for update:**
- `--object_ttl_type` -- TTL event type: create, update, property, **disable** (default: "create")
- `--object_ttl_time` -- Time to live in seconds (set together with type to enable TTL)
Expand Down
254 changes: 254 additions & 0 deletions test/unittests/test_managers/test_collection_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,146 @@ def test_update_collection_with_ttl_disable_type(mock_client, mock_wvc_object_tt
mock_wvc_object_ttl["reconfigure"].disable.assert_called_once()


def test_create_collection_with_async_replication_config(
mock_client, mock_wvc_object_ttl
):
"""Test creating a collection with async replication config parameters."""
mock_collections = MagicMock()
mock_client.collections = mock_collections
mock_collections.exists.side_effect = [False, True]
mock_client.get_meta.return_value = {"version": "1.36.0"}

manager = CollectionManager(mock_client)

async_config = {"max_workers": 10, "frequency": 60, "propagation_concurrency": 4}

manager.create_collection(
collection="TestCollection",
replication_factor=3,
vector_index="hnsw",
async_enabled=True,
async_replication_config=async_config,
)

mock_collections.create.assert_called_once()
create_call_kwargs = mock_collections.create.call_args.kwargs
assert create_call_kwargs["name"] == "TestCollection"
assert create_call_kwargs["replication_config"].asyncEnabled is True
# Verify async_config is set on the replication config
repl_config = create_call_kwargs["replication_config"]
assert repl_config.asyncConfig is not None
assert repl_config.asyncConfig.maxWorkers == 10
assert repl_config.asyncConfig.frequency == 60
assert repl_config.asyncConfig.propagationConcurrency == 4


def test_create_collection_without_async_replication_config(
mock_client, mock_wvc_object_ttl
):
"""Test creating a collection without async replication config passes None."""
mock_collections = MagicMock()
mock_client.collections = mock_collections
mock_collections.exists.side_effect = [False, True]

manager = CollectionManager(mock_client)

manager.create_collection(
collection="TestCollection",
replication_factor=3,
vector_index="hnsw",
async_enabled=True,
)

mock_collections.create.assert_called_once()
repl_config = mock_collections.create.call_args.kwargs["replication_config"]
assert repl_config.asyncConfig is None


def test_create_collection_async_replication_config_requires_async_enabled(
mock_client,
):
"""Test that async_replication_config is rejected when async_enabled is False."""
mock_collections = MagicMock()
mock_client.collections = mock_collections
mock_collections.exists.return_value = False

manager = CollectionManager(mock_client)

with pytest.raises(Exception, match="requires --async_enabled"):
manager.create_collection(
collection="TestCollection",
replication_factor=3,
vector_index="hnsw",
async_enabled=False,
async_replication_config={"max_workers": 10},
)

mock_collections.create.assert_not_called()


def test_update_collection_with_async_replication_config(
mock_client, mock_wvc_object_ttl
):
"""Test updating a collection with async replication config parameters."""
mock_collections = MagicMock()
mock_client.collections = mock_collections
mock_client.collections.exists.side_effect = [True, True]
mock_client.get_meta.return_value = {"version": "1.36.0"}

mock_collection = MagicMock()
mock_client.collections.get.return_value = mock_collection
mock_collection.config.get.return_value = MagicMock(
replication_config=MagicMock(factor=3),
multi_tenancy_config=MagicMock(
enabled=False, auto_tenant_creation=False, auto_tenant_activation=False
),
)

manager = CollectionManager(mock_client)

async_config = {"max_workers": 20, "propagation_batch_size": 100}

manager.update_collection(
collection="TestCollection",
async_replication_config=async_config,
)

mock_collection.config.update.assert_called_once()
repl_config = mock_collection.config.update.call_args.kwargs["replication_config"]
assert repl_config.asyncConfig is not None
assert repl_config.asyncConfig.maxWorkers == 20
assert repl_config.asyncConfig.propagationBatchSize == 100


def test_update_collection_without_async_replication_config(
mock_client, mock_wvc_object_ttl
):
"""Test updating a collection without async replication config passes None."""
mock_collections = MagicMock()
mock_client.collections = mock_collections
mock_client.collections.exists.side_effect = [True, True]

mock_collection = MagicMock()
mock_client.collections.get.return_value = mock_collection
mock_collection.config.get.return_value = MagicMock(
replication_config=MagicMock(factor=3),
multi_tenancy_config=MagicMock(
enabled=False, auto_tenant_creation=False, auto_tenant_activation=False
),
)

manager = CollectionManager(mock_client)

manager.update_collection(
collection="TestCollection",
description="Updated",
)

mock_collection.config.update.assert_called_once()
repl_config = mock_collection.config.update.call_args.kwargs["replication_config"]
assert repl_config.asyncConfig is None


def test_create_collection_with_hfresh_defaults(mock_client, mock_wvc_object_ttl):
"""Test creating a collection with hfresh vector index using default parameters."""
mock_collections = MagicMock()
Expand Down Expand Up @@ -860,3 +1000,117 @@ def test_create_collection_with_hfresh_invalid_distance_metric(

assert "Invalid distance_metric: 'invalid_metric'" in str(exc_info.value)
mock_collections.create.assert_not_called()


def test_update_collection_async_replication_config_rejected_when_async_false(
mock_client,
):
"""Test that async_replication_config is rejected when async_enabled is explicitly False."""
mock_collections = MagicMock()
mock_client.collections = mock_collections
mock_client.collections.exists.return_value = True

manager = CollectionManager(mock_client)

with pytest.raises(Exception, match="cannot be used when --async_enabled is False"):
manager.update_collection(
collection="TestCollection",
async_enabled=False,
async_replication_config={"max_workers": 10},
)

mock_collections.get.return_value.config.update.assert_not_called()


def test_create_collection_async_replication_config_warns_on_old_version(
mock_client, mock_wvc_object_ttl, capsys
):
"""Warn when async_replication_config is used against a server older than v1.36.0."""
mock_collections = MagicMock()
mock_client.collections = mock_collections
mock_collections.exists.side_effect = [False, True]
mock_client.get_meta.return_value = {"version": "1.34.0"}

manager = CollectionManager(mock_client)

manager.create_collection(
collection="TestCollection",
replication_factor=3,
vector_index="hnsw",
async_enabled=True,
async_replication_config={"max_workers": 10},
)

captured = capsys.readouterr()
assert "Warning: --async_replication_config requires Weaviate >= v1.36.0" in (
captured.out + captured.err
)
mock_collections.create.assert_called_once()


def test_update_collection_async_replication_config_reset(
mock_client, mock_wvc_object_ttl
):
"""Reset (empty dict) calls Reconfigure.Replication.async_config() with no kwargs."""
mock_collections = MagicMock()
mock_client.collections = mock_collections
mock_client.collections.exists.side_effect = [True, True]
mock_client.get_meta.return_value = {"version": "1.36.0"}

mock_collection = MagicMock()
mock_client.collections.get.return_value = mock_collection
mock_collection.config.get.return_value = MagicMock(
replication_config=MagicMock(factor=3),
multi_tenancy_config=MagicMock(
enabled=False, auto_tenant_creation=False, auto_tenant_activation=False
),
)

manager = CollectionManager(mock_client)

with patch.object(
wvc.Reconfigure.Replication,
"async_config",
wraps=wvc.Reconfigure.Replication.async_config,
) as mock_async_config:
manager.update_collection(
collection="TestCollection",
async_replication_config={},
)

mock_async_config.assert_called_once_with()
mock_collection.config.update.assert_called_once()
repl_config = mock_collection.config.update.call_args.kwargs["replication_config"]
assert repl_config.asyncConfig is not None


def test_update_collection_async_replication_config_warns_on_old_version(
mock_client, mock_wvc_object_ttl, capsys
):
"""Warn when async_replication_config is used against a server older than v1.36.0."""
mock_collections = MagicMock()
mock_client.collections = mock_collections
mock_client.collections.exists.side_effect = [True, True]
mock_client.get_meta.return_value = {"version": "1.34.0"}

mock_collection = MagicMock()
mock_client.collections.get.return_value = mock_collection
mock_collection.config.get.return_value = MagicMock(
replication_config=MagicMock(factor=3),
multi_tenancy_config=MagicMock(
enabled=False, auto_tenant_creation=False, auto_tenant_activation=False
),
)

manager = CollectionManager(mock_client)

manager.update_collection(
collection="TestCollection",
async_replication_config={"max_workers": 10},
)

captured = capsys.readouterr()
assert "Warning: --async_replication_config requires Weaviate >= v1.36.0" in (
captured.out + captured.err
)
mock_collection.config.update.assert_called_once()
Loading
Loading