Skip to content

Commit 539606a

Browse files
authored
Merge pull request #133 from weaviate/jose/fix-collection-alias-data-creation
Support collection aliases in all data operations
2 parents d87cdfb + fc3375e commit 539606a

8 files changed

Lines changed: 167 additions & 16 deletions

File tree

.claude/skills/operating-weaviate-cli/SKILL.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@ Key query options: `--properties "title,keywords"`, `--tenants "T1"`, `--target_
183183

184184
**Note**: `--consistency_level` values must be **lowercase**: `one`, `quorum`, `all` (not `ONE`, `QUORUM`, `ALL`).
185185

186+
**Alias support**: The `--collection` flag accepts collection aliases in all data commands. If the name isn't a direct collection, the CLI checks the alias list automatically.
187+
186188
See [references/data.md](references/data.md) and [references/search.md](references/search.md).
187189

188190
### Tenants
@@ -213,7 +215,7 @@ weaviate-cli restore backup --backend s3 --backup_id my-backup --wait --json
213215
weaviate-cli cancel backup --backend s3 --backup_id my-backup --json
214216
```
215217

216-
Backends: `s3`, `gcs`, `filesystem`. Options: `--include`, `--exclude`, `--wait`, `--cpu_for_backup N`
218+
Backends: `s3`, `gcs`, `filesystem`. Options: `--include`, `--exclude`, `--wait`, `--cpu_for_backup N`, `--override-alias`
217219

218220
See [references/backups.md](references/backups.md).
219221

.claude/skills/operating-weaviate-cli/references/backups.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ weaviate-cli get backup --backend s3 --backup_id my-backup --restore --json
2323
```bash
2424
weaviate-cli restore backup --backend s3 --backup_id my-backup --wait --json
2525
weaviate-cli restore backup --backend s3 --backup_id my-backup --include "Movies" --wait --json
26+
weaviate-cli restore backup --backend s3 --backup_id my-backup --override-alias --wait --json
2627
```
2728

2829
## Cancel Backup
@@ -44,6 +45,7 @@ weaviate-cli cancel backup --backend s3 --backup_id my-backup --json
4445
- `--backend`, `--backup_id` -- Same as create
4546
- `--include`, `--exclude` -- Filter which collections to restore
4647
- `--wait` -- Wait for completion
48+
- `--override-alias` -- Override alias conflicts during restore. Use when the backup contains a collection with an alias that differs from the current cluster state (e.g., the backup had alias `Movies` pointing to `Movies_v1`, but the cluster now has `Movies` pointing to `Movies_v2`). Without this flag, the restore will fail due to the alias conflict.
4749

4850
## Prerequisites
4951

.claude/skills/operating-weaviate-cli/references/data.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,19 @@ weaviate-cli delete data --collection "Movies" --tenants "T1,T2" --limit 50 --js
5050
3. **For multi-tenant data ingestion:** tenants must be in `hot`/`active` state
5151
4. **For vectorizer-based ingestion (non-randomize):** the collection uses a built-in dataset; vectorizer API keys may be needed
5252

53+
## Using Aliases
54+
55+
The `--collection` flag accepts aliases in all data commands (`create data`, `query data`, `update data`, `delete data`). If the given name doesn't match a collection directly, the CLI checks the alias list and resolves it transparently.
56+
57+
```bash
58+
# Create an alias, then use it for data operations
59+
weaviate-cli create alias MyAlias Movies --json
60+
weaviate-cli create data --collection MyAlias --limit 100 --randomize --json
61+
weaviate-cli query data --collection MyAlias --search_type fetch --limit 10 --json
62+
weaviate-cli update data --collection MyAlias --limit 50 --randomize --json
63+
weaviate-cli delete data --collection MyAlias --limit 50 --json
64+
```
65+
5366
## Notes
5467

5568
- `--randomize` generates synthetic data with random vectors -- useful for testing

test/unittests/test_managers/test_data_manager.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,118 @@ def test_fallback_no_tenants_with_auto_creation_returns_empty(self) -> None:
223223
assert result == []
224224

225225

226+
# ---------------------------------------------------------------------------
227+
# Alias resolution in data operations
228+
# ---------------------------------------------------------------------------
229+
230+
231+
class TestAliasResolution:
232+
"""Unit tests for alias resolution in create/update/delete/query data."""
233+
234+
def _setup_alias_mock(self, mock_client, alias_name="MyAlias"):
235+
"""Set up a mock client where collection doesn't exist but alias does."""
236+
mock_collections = MagicMock()
237+
mock_collections.exists.return_value = False
238+
mock_client.collections = mock_collections
239+
240+
mock_alias = MagicMock()
241+
mock_alias.list_all.return_value = {alias_name: MagicMock()}
242+
mock_client.alias = mock_alias
243+
244+
mock_collection = MagicMock()
245+
mock_collection.config.get.return_value = MagicMock(
246+
multi_tenancy_config=MagicMock(
247+
enabled=False,
248+
auto_tenant_creation=False,
249+
auto_tenant_activation=False,
250+
)
251+
)
252+
mock_client.collections.get.return_value = mock_collection
253+
return mock_collection
254+
255+
def test_create_data_with_alias(self, mock_client):
256+
self._setup_alias_mock(mock_client)
257+
manager = DataManager(mock_client)
258+
manager.create_data(collection="MyAlias", limit=10, randomize=True)
259+
mock_client.collections.get.assert_called_once_with("MyAlias")
260+
261+
def test_update_data_with_alias(self, mock_client):
262+
self._setup_alias_mock(mock_client)
263+
manager = DataManager(mock_client)
264+
manager.update_data(collection="MyAlias", limit=10, randomize=True)
265+
mock_client.collections.get.assert_called_once_with("MyAlias")
266+
267+
def test_delete_data_with_alias(self, mock_client):
268+
col = self._setup_alias_mock(mock_client)
269+
col.config.get.return_value.multi_tenancy_config.enabled = False
270+
# delete_data iterates objects, mock the iterator
271+
col.iterator.return_value = iter([])
272+
manager = DataManager(mock_client)
273+
manager.delete_data(collection="MyAlias", limit=10)
274+
mock_client.collections.get.assert_called_once_with("MyAlias")
275+
276+
def test_query_data_with_alias(self, mock_client):
277+
col = self._setup_alias_mock(mock_client)
278+
col.config.get.return_value.multi_tenancy_config.enabled = False
279+
col.query.fetch_objects.return_value = MagicMock(objects=[])
280+
manager = DataManager(mock_client)
281+
manager.query_data(collection="MyAlias", search_type="fetch", limit=5)
282+
mock_client.collections.get.assert_called_once_with("MyAlias")
283+
284+
def _setup_not_found_mock(self, mock_client):
285+
"""Set up a mock client where neither collection nor alias exists."""
286+
mock_collections = MagicMock()
287+
mock_collections.exists.return_value = False
288+
mock_client.collections = mock_collections
289+
mock_alias = MagicMock()
290+
mock_alias.list_all.return_value = {}
291+
mock_client.alias = mock_alias
292+
293+
def test_create_data_not_collection_not_alias_raises(self, mock_client):
294+
self._setup_not_found_mock(mock_client)
295+
manager = DataManager(mock_client)
296+
with pytest.raises(Exception, match="does not exist"):
297+
manager.create_data(collection="NonExistent", limit=10, randomize=True)
298+
299+
def test_update_data_not_collection_not_alias_raises(self, mock_client):
300+
self._setup_not_found_mock(mock_client)
301+
manager = DataManager(mock_client)
302+
with pytest.raises(Exception, match="does not exist"):
303+
manager.update_data(collection="NonExistent", limit=10, randomize=True)
304+
305+
def test_delete_data_not_collection_not_alias_raises(self, mock_client):
306+
self._setup_not_found_mock(mock_client)
307+
manager = DataManager(mock_client)
308+
with pytest.raises(Exception, match="does not exist"):
309+
manager.delete_data(collection="NonExistent", limit=10)
310+
311+
def test_query_data_not_collection_not_alias_raises(self, mock_client):
312+
self._setup_not_found_mock(mock_client)
313+
manager = DataManager(mock_client)
314+
with pytest.raises(Exception, match="does not exist"):
315+
manager.query_data(collection="NonExistent", search_type="fetch", limit=5)
316+
317+
def test_create_data_direct_collection_skips_alias_check(self, mock_client):
318+
"""When collection exists directly, alias.list_all should not be called."""
319+
mock_collections = MagicMock()
320+
mock_collections.exists.return_value = True
321+
mock_client.collections = mock_collections
322+
mock_alias = MagicMock()
323+
mock_client.alias = mock_alias
324+
mock_collection = MagicMock()
325+
mock_collection.config.get.return_value = MagicMock(
326+
multi_tenancy_config=MagicMock(
327+
enabled=False,
328+
auto_tenant_creation=False,
329+
auto_tenant_activation=False,
330+
)
331+
)
332+
mock_client.collections.get.return_value = mock_collection
333+
manager = DataManager(mock_client)
334+
manager.create_data(collection="DirectCollection", limit=10, randomize=True)
335+
mock_client.alias.list_all.assert_not_called()
336+
337+
226338
def test_ingest_data(mock_client):
227339
manager = DataManager(mock_client)
228340
mock_collections = MagicMock()

weaviate_cli/commands/restore.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,18 @@ def restore() -> None:
3838
default=RestoreBackupDefaults.exclude,
3939
help="Collection to exclude in backup (default: None).",
4040
)
41+
@click.option(
42+
"--override-alias",
43+
is_flag=True,
44+
help="Override the alias of the collection (default: False).",
45+
)
4146
@click.option(
4247
"--json", "json_output", is_flag=True, default=False, help="Output in JSON format."
4348
)
4449
@click.pass_context
45-
def restore_backup_cli(ctx, backend, include, exclude, backup_id, wait, json_output):
50+
def restore_backup_cli(
51+
ctx, backend, include, exclude, backup_id, wait, override_alias, json_output
52+
):
4653
"""Restore a backup in Weaviate."""
4754

4855
client = None
@@ -55,6 +62,7 @@ def restore_backup_cli(ctx, backend, include, exclude, backup_id, wait, json_out
5562
include=include,
5663
exclude=exclude,
5764
wait=wait,
65+
override_alias=override_alias,
5866
json_output=json_output,
5967
)
6068
except Exception as e:

weaviate_cli/defaults.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ class RestoreBackupDefaults:
237237
wait: bool = False
238238
include: Optional[str] = None
239239
exclude: Optional[str] = None
240+
override_alias: bool = False
240241

241242

242243
@dataclass

weaviate_cli/managers/backup_manager.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
class BackupManager:
1616
def __init__(self, client: WeaviateClient) -> None:
17-
self.client = client
17+
self.client: WeaviateClient = client
1818

1919
def create_backup(
2020
self,
@@ -81,6 +81,7 @@ def restore_backup(
8181
include: Optional[str] = RestoreBackupDefaults.include,
8282
exclude: Optional[str] = RestoreBackupDefaults.exclude,
8383
wait: bool = RestoreBackupDefaults.wait,
84+
override_alias: bool = RestoreBackupDefaults.override_alias,
8485
json_output: bool = False,
8586
) -> None:
8687

@@ -89,6 +90,7 @@ def restore_backup(
8990
backend=backend,
9091
include_collections=include.split(",") if include else None,
9192
exclude_collections=exclude.split(",") if exclude else None,
93+
overwrite_alias=override_alias,
9294
wait_for_completion=wait,
9395
)
9496

weaviate_cli/managers/data_manager.py

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,7 @@ def __import_json(
572572
json_output: bool = False,
573573
) -> int:
574574
counter = 0
575+
575576
properties: List[wvc.Property] = collection.config.get().properties
576577

577578
try:
@@ -657,8 +658,9 @@ def __ingest_data(
657658
click.echo(f"Generating and ingesting {num_objects} objects")
658659
start_time = time.time()
659660

660-
# Determine vectorizer setup
661+
# Determine vector dimensions based on vectorizer
661662
config = collection.config.get()
663+
662664
if not config.vectorizer and config.vector_config:
663665
named_vectors = list(config.vector_config.keys())
664666
vectorizer = config.vector_config[
@@ -821,9 +823,11 @@ def create_data(
821823
) -> Collection:
822824

823825
if not self.client.collections.exists(collection):
824-
raise Exception(
825-
f"Class '{collection}' does not exist in Weaviate. Create first using <create class> command"
826-
)
826+
alias_list = self.client.alias.list_all()
827+
if collection not in alias_list.keys():
828+
raise Exception(
829+
f"Class '{collection}' does not exist in Weaviate. Create first using <create class> command"
830+
)
827831

828832
col: Collection = self.client.collections.get(collection)
829833
mt_config = col.config.get().multi_tenancy_config
@@ -1123,9 +1127,11 @@ def update_data(
11231127
) -> None:
11241128

11251129
if not self.client.collections.exists(collection):
1126-
raise Exception(
1127-
f"Class '{collection}' does not exist in Weaviate. Create first using ./create_class.py"
1128-
)
1130+
alias_list = self.client.alias.list_all()
1131+
if collection not in alias_list.keys():
1132+
raise Exception(
1133+
f"Class '{collection}' does not exist in Weaviate. Create first using ./create_class.py"
1134+
)
11291135

11301136
col: Collection = self.client.collections.get(collection)
11311137
try:
@@ -1277,9 +1283,11 @@ def delete_data(
12771283
) -> None:
12781284

12791285
if not self.client.collections.exists(collection):
1280-
raise Exception(
1281-
f"Class '{collection}' does not exist in Weaviate. Create first using <create class> command."
1282-
)
1286+
alias_list = self.client.alias.list_all()
1287+
if collection not in alias_list.keys():
1288+
raise Exception(
1289+
f"Class '{collection}' does not exist in Weaviate. Create first using <create class> command."
1290+
)
12831291

12841292
col: Collection = self.client.collections.get(collection)
12851293
mt_enabled = col.config.get().multi_tenancy_config.enabled
@@ -1297,6 +1305,7 @@ def delete_data(
12971305
tenants = tenants_list if tenants_list is not None else existing_tenants
12981306

12991307
total_deleted = 0
1308+
13001309
for tenant in tenants:
13011310
if tenant == "None":
13021311
ret = self.__delete_data( # NOTE: call the correct delete impl
@@ -1408,9 +1417,11 @@ def query_data(
14081417
) -> None:
14091418

14101419
if not self.client.collections.exists(collection):
1411-
raise Exception(
1412-
f"Class '{collection}' does not exist in Weaviate. Create first using <create class> command."
1413-
)
1420+
alias_list = self.client.alias.list_all()
1421+
if collection not in alias_list.keys():
1422+
raise Exception(
1423+
f"Class '{collection}' does not exist in Weaviate. Create first using <create class> command."
1424+
)
14141425

14151426
col: Collection = self.client.collections.get(collection)
14161427
mt_enabled = col.config.get().multi_tenancy_config.enabled

0 commit comments

Comments
 (0)