Skip to content

Commit 4bff06b

Browse files
jfrancoaclaude
andcommitted
fix: handle missing asyncConfig in merge_with_existing
When updating a collection that was created without async replication config, the existing schema has no asyncConfig key. merge_with_existing would raise KeyError trying to access schema["asyncConfig"]. Use schema.get(cls_field, {}) to gracefully handle missing nested _ConfigUpdateModel fields. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0b99cef commit 4bff06b

3 files changed

Lines changed: 62 additions & 1 deletion

File tree

integration/test_collection_config.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1634,13 +1634,49 @@ def test_replication_config_remove_async_config(collection_factory: CollectionFa
16341634
collection.config.update(
16351635
replication_config=Reconfigure.replication(
16361636
async_enabled=False,
1637+
async_config=None,
16371638
),
16381639
)
16391640
config = collection.config.get()
16401641
assert config.replication_config.async_enabled is False
16411642
assert config.replication_config.async_config is None
16421643

16431644

1645+
def test_replication_config_add_async_config_to_existing_collection(
1646+
collection_factory: CollectionFactory,
1647+
) -> None:
1648+
"""Test updating a collection that was created without async_config to add one.
1649+
1650+
This covers the case where the existing schema has no asyncConfig key
1651+
and merge_with_existing must handle the missing field gracefully.
1652+
"""
1653+
collection_dummy = collection_factory("dummy")
1654+
if collection_dummy._connection._weaviate_version.is_lower_than(1, 36, 0):
1655+
pytest.skip("async replication config requires Weaviate >= 1.36.0")
1656+
1657+
# Create without async_config
1658+
collection = collection_factory(
1659+
replication_config=Configure.replication(factor=1, async_enabled=True),
1660+
)
1661+
config = collection.config.get()
1662+
assert config.replication_config.async_config is None
1663+
1664+
# Update to add async_config
1665+
collection.config.update(
1666+
replication_config=Reconfigure.replication(
1667+
async_config=Reconfigure.Replication.async_config(
1668+
max_workers=12,
1669+
propagation_concurrency=4,
1670+
),
1671+
),
1672+
)
1673+
config = collection.config.get()
1674+
assert config.replication_config.async_config is not None
1675+
ac = config.replication_config.async_config
1676+
assert ac.max_workers == 12
1677+
assert ac.propagation_concurrency == 4
1678+
1679+
16441680
def test_update_property_descriptions(collection_factory: CollectionFactory) -> None:
16451681
collection = collection_factory(
16461682
vectorizer_config=Configure.Vectorizer.none(),

test/collection/test_config.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2986,6 +2986,31 @@ def test_replication_config_to_dict_without_async_config() -> None:
29862986
assert "asyncConfig" not in d
29872987

29882988

2989+
def test_replication_config_update_merge_with_missing_async_config() -> None:
2990+
"""Test that merge_with_existing handles a schema without asyncConfig.
2991+
2992+
When a collection was created without async replication config and we
2993+
update it to add one, the existing schema won't have the asyncConfig key.
2994+
merge_with_existing must not raise KeyError in this case.
2995+
"""
2996+
update = Reconfigure.replication(
2997+
async_config=Reconfigure.Replication.async_config(
2998+
max_workers=12,
2999+
propagation_concurrency=4,
3000+
),
3001+
)
3002+
# Simulate an existing schema that has no asyncConfig key
3003+
existing_schema = {
3004+
"factor": 3,
3005+
"asyncEnabled": True,
3006+
"deletionStrategy": "NoAutomatedResolution",
3007+
}
3008+
result = update.merge_with_existing(existing_schema)
3009+
assert result["asyncConfig"]["maxWorkers"] == 12
3010+
assert result["asyncConfig"]["propagationConcurrency"] == 4
3011+
assert result["factor"] == 3
3012+
3013+
29893014
def test_nested_property_with_id_name_is_allowed() -> None:
29903015
"""A nested property named 'id' must not raise — only top-level 'id' is reserved."""
29913016
prop = Property(

weaviate/collections/classes/config_base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def merge_with_existing(self, schema: Dict[str, Any]) -> Dict[str, Any]:
4242
)
4343
schema[quantizer]["enabled"] = False
4444
elif isinstance(val, _ConfigUpdateModel):
45-
schema[cls_field] = val.merge_with_existing(schema[cls_field])
45+
schema[cls_field] = val.merge_with_existing(schema.get(cls_field, {}))
4646
else:
4747
pass # ignore unknown types so that individual classes can be extended
4848
return schema

0 commit comments

Comments
 (0)