weaviate-cli 3.1.2__tar.gz → 3.1.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/PKG-INFO +1 -1
  2. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli/commands/create.py +33 -2
  3. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli/commands/delete.py +8 -2
  4. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli/commands/update.py +1 -1
  5. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli/defaults.py +8 -4
  6. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli/managers/collection_manager.py +1 -1
  7. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli/managers/data_manager.py +67 -24
  8. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli/managers/tenant_manager.py +83 -34
  9. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli.egg-info/PKG-INFO +1 -1
  10. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/.github/dependabot.yml +0 -0
  11. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/.github/workflows/main.yaml +0 -0
  12. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/.github/workflows/release.yaml +0 -0
  13. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/.gitignore +0 -0
  14. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/.pre-commit-config.yaml +0 -0
  15. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/CONTRIBUTING.md +0 -0
  16. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/Dockerfile +0 -0
  17. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/LICENSE +0 -0
  18. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/MANIFEST.in +0 -0
  19. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/Makefile +0 -0
  20. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/README.md +0 -0
  21. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/cli.py +0 -0
  22. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/publish.md +0 -0
  23. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/pyproject.toml +0 -0
  24. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/requirements-dev.txt +0 -0
  25. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/setup.cfg +0 -0
  26. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/setup.py +0 -0
  27. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/test/README.md +0 -0
  28. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/test/__init__.py +0 -0
  29. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/test/integration/test_integration.py +0 -0
  30. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/test/unittests/conftest.py +0 -0
  31. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/test/unittests/test_cli.py +0 -0
  32. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/test/unittests/test_defaults.py +0 -0
  33. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/test/unittests/test_managers/test_collection_manager.py +0 -0
  34. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/test/unittests/test_managers/test_config_manager.py +0 -0
  35. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/test/unittests/test_managers/test_data_manager.py +0 -0
  36. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/test/unittests/test_managers/test_node_manager.py +0 -0
  37. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/test/unittests/test_managers/test_shard_manager.py +0 -0
  38. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/test/unittests/test_utils.py +0 -0
  39. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli/__init__.py +0 -0
  40. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli/commands/__init__.py +0 -0
  41. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli/commands/assign.py +0 -0
  42. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli/commands/cancel.py +0 -0
  43. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli/commands/get.py +0 -0
  44. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli/commands/query.py +0 -0
  45. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli/commands/restore.py +0 -0
  46. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli/commands/revoke.py +0 -0
  47. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli/completion/__init__.py +0 -0
  48. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli/completion/complete.py +0 -0
  49. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli/datasets/__init__.py +0 -0
  50. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli/datasets/movies.json +0 -0
  51. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli/managers/__init__.py +0 -0
  52. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli/managers/backup_manager.py +0 -0
  53. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli/managers/config_manager.py +0 -0
  54. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli/managers/node_manager.py +0 -0
  55. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli/managers/role_manager.py +0 -0
  56. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli/managers/shard_manager.py +0 -0
  57. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli/managers/user_manager.py +0 -0
  58. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli/types/models.py +0 -0
  59. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli/utils.py +0 -0
  60. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli.egg-info/SOURCES.txt +0 -0
  61. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli.egg-info/dependency_links.txt +0 -0
  62. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli.egg-info/entry_points.txt +0 -0
  63. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli.egg-info/not-zip-safe +0 -0
  64. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli.egg-info/requires.txt +0 -0
  65. {weaviate_cli-3.1.2 → weaviate_cli-3.1.4}/weaviate_cli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: weaviate-cli
3
- Version: 3.1.2
3
+ Version: 3.1.4
4
4
  Summary: Command line interface to interact with weaviate
5
5
  Home-page: https://github.com/weaviate/weaviate-cli
6
6
  Download-URL: https://github.com/weaviate/weaviate-cli
@@ -183,20 +183,28 @@ def create_collection_cli(
183
183
  @click.option(
184
184
  "--tenant_suffix",
185
185
  default=CreateTenantsDefaults.tenant_suffix,
186
- help="The suffix to add to the tenant name (default: 'Tenant--').",
186
+ help="The suffix to add to the tenant name (default: 'Tenant-').",
187
187
  )
188
188
  @click.option(
189
189
  "--number_tenants",
190
190
  default=CreateTenantsDefaults.number_tenants,
191
191
  help="Number of tenants to create (default: 100).",
192
192
  )
193
+ @click.option(
194
+ "--tenant_batch_size",
195
+ default=CreateTenantsDefaults.tenant_batch_size,
196
+ type=int,
197
+ help="Number of tenants to create in each batch (default: None, all tenants will be created in one batch).",
198
+ )
193
199
  @click.option(
194
200
  "--state",
195
201
  default=CreateTenantsDefaults.state,
196
202
  type=click.Choice(["hot", "active", "cold", "inactive", "frozen", "offloaded"]),
197
203
  )
198
204
  @click.pass_context
199
- def create_tenants_cli(ctx, collection, tenant_suffix, number_tenants, state):
205
+ def create_tenants_cli(
206
+ ctx, collection, tenant_suffix, number_tenants, tenant_batch_size, state
207
+ ):
200
208
  """Create tenants in Weaviate."""
201
209
 
202
210
  client = None
@@ -208,6 +216,7 @@ def create_tenants_cli(ctx, collection, tenant_suffix, number_tenants, state):
208
216
  collection=collection,
209
217
  tenant_suffix=tenant_suffix,
210
218
  number_tenants=number_tenants,
219
+ tenant_batch_size=tenant_batch_size,
211
220
  state=state,
212
221
  )
213
222
  except Exception as e:
@@ -301,6 +310,16 @@ def create_backup_cli(ctx, backend, backup_id, include, exclude, wait, cpu_for_b
301
310
  default=CreateDataDefaults.auto_tenants,
302
311
  help="Number of tenants for which we will send data. NOTE: Requires class with --auto_tenant_creation (default: 0).",
303
312
  )
313
+ @click.option(
314
+ "--tenants",
315
+ default=None,
316
+ help="Comma separated list of tenants to send data to.",
317
+ )
318
+ @click.option(
319
+ "--tenant_suffix",
320
+ default=CreateTenantsDefaults.tenant_suffix,
321
+ help="The suffix to add to the tenant name (default: 'Tenant-'). Only used if --auto_tenants is provided.",
322
+ )
304
323
  @click.option(
305
324
  "--vector_dimensions",
306
325
  default=CreateDataDefaults.vector_dimensions,
@@ -311,6 +330,12 @@ def create_backup_cli(ctx, backend, backup_id, include, exclude, wait, cpu_for_b
311
330
  default=None,
312
331
  help="UUID of the object to be used when the data is randomized. It requires --limit=1 and --randomize to be enabled.",
313
332
  )
333
+ @click.option(
334
+ "--wait_for_indexing",
335
+ is_flag=True,
336
+ default=CreateDataDefaults.wait_for_indexing,
337
+ help="Wait for the indexing to complete before returning.",
338
+ )
314
339
  @click.pass_context
315
340
  def create_data_cli(
316
341
  ctx,
@@ -319,8 +344,11 @@ def create_data_cli(
319
344
  consistency_level,
320
345
  randomize,
321
346
  auto_tenants,
347
+ tenants,
348
+ tenant_suffix,
322
349
  vector_dimensions,
323
350
  uuid,
351
+ wait_for_indexing,
324
352
  ):
325
353
  """Ingest data into a collection in Weaviate."""
326
354
 
@@ -349,8 +377,11 @@ def create_data_cli(
349
377
  consistency_level=consistency_level,
350
378
  randomize=randomize,
351
379
  auto_tenants=auto_tenants,
380
+ tenants_list=tenants.split(",") if tenants else None,
352
381
  vector_dimensions=vector_dimensions,
353
382
  uuid=uuid,
383
+ tenant_suffix=tenant_suffix,
384
+ wait_for_indexing=wait_for_indexing,
354
385
  )
355
386
  except Exception as e:
356
387
  click.echo(f"Error: {e}")
@@ -60,7 +60,7 @@ def delete_collection_cli(ctx: click.Context, collection: str, all: bool) -> Non
60
60
  @click.option(
61
61
  "--tenant_suffix",
62
62
  default=DeleteTenantsDefaults.tenant_suffix,
63
- help="The suffix to add to the tenant name (default: 'Tenant--').",
63
+ help="The suffix to add to the tenant name (default: 'Tenant-').",
64
64
  )
65
65
  @click.option(
66
66
  "--number_tenants",
@@ -110,13 +110,18 @@ def delete_tenants_cli(
110
110
  type=click.Choice(["quorum", "all", "one"]),
111
111
  help="Consistency level (default: 'quorum').",
112
112
  )
113
+ @click.option(
114
+ "--tenants",
115
+ default=None,
116
+ help="Comma separated list of tenants to delete data from.",
117
+ )
113
118
  @click.option(
114
119
  "--uuid",
115
120
  default=DeleteDataDefaults.uuid,
116
121
  help="UUID of the oject to be deleted. If provided, --limit will be ignored.",
117
122
  )
118
123
  @click.pass_context
119
- def delete_data_cli(ctx, collection, limit, consistency_level, uuid):
124
+ def delete_data_cli(ctx, collection, limit, consistency_level, tenants, uuid):
120
125
  """Delete data from a collection in Weaviate."""
121
126
 
122
127
  client = None
@@ -128,6 +133,7 @@ def delete_data_cli(ctx, collection, limit, consistency_level, uuid):
128
133
  collection=collection,
129
134
  limit=limit,
130
135
  consistency_level=consistency_level,
136
+ tenants_list=tenants.split(",") if tenants else None,
131
137
  uuid=uuid,
132
138
  )
133
139
  except Exception as e:
@@ -121,7 +121,7 @@ def update_collection_cli(
121
121
  @click.option(
122
122
  "--tenant_suffix",
123
123
  default=UpdateTenantsDefaults.tenant_suffix,
124
- help="The suffix to add to the tenant name (default: 'Tenant--').",
124
+ help="The suffix to add to the tenant name (default: 'Tenant-').",
125
125
  )
126
126
  @click.option(
127
127
  "--number_tenants",
@@ -30,6 +30,8 @@ PERMISSION_HELP_STRING = (
30
30
  " --permission read_cluster"
31
31
  )
32
32
 
33
+ MAX_OBJECTS_PER_BATCH = 5000
34
+
33
35
 
34
36
  @dataclass
35
37
  class CreateCollectionDefaults:
@@ -43,7 +45,7 @@ class CreateCollectionDefaults:
43
45
  auto_tenant_creation: bool = False
44
46
  auto_tenant_activation: bool = False
45
47
  force_auto_schema: bool = False
46
- shards: int = 1
48
+ shards: int = 0
47
49
  vectorizer: Optional[str] = None
48
50
  replication_deletion_strategy: str = "no_automated_resolution"
49
51
 
@@ -51,8 +53,9 @@ class CreateCollectionDefaults:
51
53
  @dataclass
52
54
  class CreateTenantsDefaults:
53
55
  collection: str = "Movies"
54
- tenant_suffix: str = "Tenant--"
56
+ tenant_suffix: str = "Tenant-"
55
57
  number_tenants: int = 100
58
+ tenant_batch_size: Optional[int] = None
56
59
  state: str = "active"
57
60
 
58
61
 
@@ -74,6 +77,7 @@ class CreateDataDefaults:
74
77
  randomize: bool = False
75
78
  auto_tenants: int = 0
76
79
  vector_dimensions: int = 1536
80
+ wait_for_indexing: bool = False
77
81
 
78
82
 
79
83
  @dataclass
@@ -97,7 +101,7 @@ class DeleteCollectionDefaults:
97
101
  @dataclass
98
102
  class DeleteTenantsDefaults:
99
103
  collection: str = "Movies"
100
- tenant_suffix: str = "Tenant--"
104
+ tenant_suffix: str = "Tenant-"
101
105
  number_tenants: int = 100
102
106
 
103
107
 
@@ -183,7 +187,7 @@ class UpdateCollectionDefaults:
183
187
  @dataclass
184
188
  class UpdateTenantsDefaults:
185
189
  collection: str = "Movies"
186
- tenant_suffix: str = "Tenant--"
190
+ tenant_suffix: str = "Tenant-"
187
191
  number_tenants: int = 100
188
192
  state: str = "active"
189
193
 
@@ -234,7 +234,7 @@ class CollectionManager:
234
234
  ),
235
235
  ),
236
236
  sharding_config=(
237
- wvc.Configure.sharding(desired_count=shards) if shards > 1 else None
237
+ wvc.Configure.sharding(desired_count=shards) if shards > 0 else None
238
238
  ),
239
239
  multi_tenancy_config=wvc.Configure.multi_tenancy(
240
240
  enabled=multitenant,
@@ -10,16 +10,20 @@ from weaviate.classes.query import MetadataQuery
10
10
  from weaviate.collections.classes.tenants import TenantActivityStatus
11
11
  from typing import Dict, List, Optional, Union, Any
12
12
  import weaviate.classes.config as wvc
13
+ from weaviate.classes.query import Filter
13
14
  from weaviate.collections import Collection
14
15
  from datetime import datetime, timedelta
15
16
  from weaviate_cli.defaults import (
17
+ MAX_OBJECTS_PER_BATCH,
16
18
  CreateDataDefaults,
19
+ CreateTenantsDefaults,
17
20
  QueryDataDefaults,
18
21
  UpdateDataDefaults,
19
22
  DeleteDataDefaults,
20
23
  )
21
24
  import importlib.resources as resources
22
25
  from pathlib import Path
26
+ import math
23
27
 
24
28
  PROPERTY_NAME_MAPPING = {
25
29
  "releaseDate": "release_date",
@@ -203,10 +207,13 @@ class DataManager:
203
207
  limit: int = CreateDataDefaults.limit,
204
208
  consistency_level: str = CreateDataDefaults.consistency_level,
205
209
  randomize: bool = CreateDataDefaults.randomize,
210
+ tenant_suffix: str = CreateTenantsDefaults.tenant_suffix,
206
211
  auto_tenants: int = CreateDataDefaults.auto_tenants,
212
+ tenants_list: Optional[List[str]] = None,
207
213
  vector_dimensions: Optional[int] = CreateDataDefaults.vector_dimensions,
208
214
  uuid: Optional[str] = None,
209
215
  named_vectors: Optional[List[str]] = None,
216
+ wait_for_indexing: bool = CreateDataDefaults.wait_for_indexing,
210
217
  ) -> Collection:
211
218
 
212
219
  if not self.client.collections.exists(collection):
@@ -217,14 +224,19 @@ class DataManager:
217
224
 
218
225
  col: Collection = self.client.collections.get(collection)
219
226
  try:
220
- tenants = [key for key in col.tenants.get().keys()]
227
+ existing_tenants = [key for key in col.tenants.get().keys()]
221
228
  except Exception as e:
222
229
  # Check if the error is due to multi-tenancy being disabled
223
230
  if "multi-tenancy is not enabled" in str(e):
224
- click.echo(
225
- f"Collection '{col.name}' does not have multi-tenancy enabled. Skipping tenant information collection."
226
- )
227
- tenants = ["None"]
231
+ if (
232
+ tenants_list is not None
233
+ or auto_tenants != CreateDataDefaults.auto_tenants
234
+ ):
235
+ raise Exception(
236
+ f"Collection '{col.name}' does not have multi-tenancy enabled. Adding data to tenants is not possible."
237
+ )
238
+
239
+ existing_tenants = ["None"]
228
240
  else:
229
241
  raise e
230
242
  if (
@@ -241,16 +253,23 @@ class DataManager:
241
253
  "all": wvc.ConsistencyLevel.ALL,
242
254
  "one": wvc.ConsistencyLevel.ONE,
243
255
  }
256
+ tenants = existing_tenants
244
257
  if auto_tenants > 0:
245
- if tenants == "None":
246
- tenants = [f"Tenant--{i}" for i in range(1, auto_tenants + 1)]
258
+ if tenants_list is not None:
259
+ raise Exception(
260
+ f"Either --tenants or --auto_tenants must be provided, not both."
261
+ )
262
+ if existing_tenants == "None":
263
+ tenants = [f"{tenant_suffix}{i}" for i in range(1, auto_tenants + 1)]
247
264
  else:
248
- if len(tenants) < auto_tenants:
265
+ if len(existing_tenants) < auto_tenants:
249
266
  tenants += [
250
- f"Tenant--{i}"
251
- for i in range(len(tenants) + 1, auto_tenants + 1)
267
+ f"{tenant_suffix}{i}"
268
+ for i in range(len(existing_tenants) + 1, auto_tenants + 1)
252
269
  ]
253
-
270
+ else:
271
+ if tenants_list is not None:
272
+ tenants = tenants_list
254
273
  for tenant in tenants:
255
274
  if tenant == "None":
256
275
  collection = self.__ingest_data(
@@ -273,7 +292,8 @@ class DataManager:
273
292
  uuid,
274
293
  named_vectors,
275
294
  )
276
-
295
+ if wait_for_indexing:
296
+ collection.batch.wait_for_vector_indexing()
277
297
  if len(collection) != limit:
278
298
  click.echo(
279
299
  f"Error occurred while ingesting data for tenant '{tenant}'. Check number of objects inserted."
@@ -402,25 +422,43 @@ class DataManager:
402
422
  print(f"Object deleted: {uuid} into class '{collection.name}'")
403
423
  return 1
404
424
 
405
- res = collection.query.fetch_objects(limit=num_objects)
406
- if len(res.objects) == 0:
407
- print(
408
- f"No objects found in class '{collection.name}'. Insert objects first using <ingest data> command"
425
+ # Calculate the number of full batches and handle any remaining objects
426
+ # Use math.ceil to ensure we process all objects, even if num_objects < MAX_OBJECTS_PER_BATCH
427
+ iterations = math.ceil(num_objects / MAX_OBJECTS_PER_BATCH)
428
+ deleted_objects = 0
429
+
430
+ for _ in range(iterations):
431
+ # Determine how many objects to fetch in this batch
432
+ batch_size = min(MAX_OBJECTS_PER_BATCH, num_objects - deleted_objects)
433
+ if batch_size <= 0:
434
+ break
435
+
436
+ res = collection.query.fetch_objects(limit=batch_size)
437
+ if len(res.objects) == 0:
438
+ click.echo(
439
+ f"No objects found in class '{collection.name}'. Insert objects first using <ingest data> command"
440
+ )
441
+ return deleted_objects
442
+
443
+ ids = [o.uuid for o in res.objects]
444
+ collection.with_consistency_level(cl).data.delete_many(
445
+ where=Filter.by_id().contains_any(ids)
409
446
  )
410
- return -1
411
- data_objects = res.objects
447
+ deleted_objects += len(ids)
412
448
 
413
- for obj in data_objects:
414
- collection.with_consistency_level(cl).data.delete_by_id(uuid=obj.uuid)
449
+ # If we've deleted fewer objects than expected, there might not be any more objects
450
+ if len(ids) < batch_size:
451
+ break
415
452
 
416
- print(f"Deleted {num_objects} objects into class '{collection.name}'")
417
- return num_objects
453
+ print(f"Deleted {deleted_objects} objects from class '{collection.name}'")
454
+ return deleted_objects
418
455
 
419
456
  def delete_data(
420
457
  self,
421
458
  collection: str = DeleteDataDefaults.collection,
422
459
  limit: int = DeleteDataDefaults.limit,
423
460
  consistency_level: str = DeleteDataDefaults.consistency_level,
461
+ tenants_list: Optional[List[str]] = None,
424
462
  uuid: Optional[str] = DeleteDataDefaults.uuid,
425
463
  ) -> None:
426
464
 
@@ -433,14 +471,14 @@ class DataManager:
433
471
 
434
472
  col: Collection = self.client.collections.get(collection)
435
473
  try:
436
- tenants = [key for key in col.tenants.get().keys()]
474
+ existing_tenants = [key for key in col.tenants.get().keys()]
437
475
  except Exception as e:
438
476
  # Check if the error is due to multi-tenancy being disabled
439
477
  if "multi-tenancy is not enabled" in str(e):
440
478
  click.echo(
441
479
  f"Collection '{col.name}' does not have multi-tenancy enabled. Skipping tenant information collection."
442
480
  )
443
- tenants = ["None"]
481
+ existing_tenants = ["None"]
444
482
 
445
483
  cl_map = {
446
484
  "quorum": wvc.ConsistencyLevel.QUORUM,
@@ -448,6 +486,11 @@ class DataManager:
448
486
  "one": wvc.ConsistencyLevel.ONE,
449
487
  }
450
488
 
489
+ if tenants_list is not None:
490
+ tenants = tenants_list
491
+ else:
492
+ tenants = existing_tenants
493
+
451
494
  for tenant in tenants:
452
495
  if tenant == "None":
453
496
  ret = self.__delete_data(col, limit, cl_map[consistency_level], uuid)
@@ -1,3 +1,4 @@
1
+ from typing import Optional
1
2
  import click
2
3
  import semver
3
4
 
@@ -20,6 +21,7 @@ class TenantManager:
20
21
  collection: str = CreateTenantsDefaults.collection,
21
22
  tenant_suffix: str = CreateTenantsDefaults.tenant_suffix,
22
23
  number_tenants: int = CreateTenantsDefaults.number_tenants,
24
+ tenant_batch_size: Optional[int] = CreateTenantsDefaults.tenant_batch_size,
23
25
  state: str = CreateTenantsDefaults.state,
24
26
  ) -> None:
25
27
  """
@@ -33,11 +35,10 @@ class TenantManager:
33
35
 
34
36
  Raises:
35
37
  Exception: If the collection does not exist, multi-tenancy is not enabled,
36
- tenants already exist, or if there is a mismatch in tenant activity status.
38
+ or if there is a mismatch in tenant activity status.
37
39
  """
38
40
 
39
41
  if not self.client.collections.exists(collection):
40
-
41
42
  raise Exception(
42
43
  f"Class '{collection}' does not exist in Weaviate. Create first using <create class>"
43
44
  )
@@ -46,7 +47,6 @@ class TenantManager:
46
47
  collection = self.client.collections.get(collection)
47
48
 
48
49
  if not collection.config.get().multi_tenancy_config.enabled:
49
-
50
50
  raise Exception(
51
51
  f"Collection '{collection.name}' does not have multi-tenancy enabled. Recreate or modify the class with: <create class>"
52
52
  )
@@ -61,45 +61,94 @@ class TenantManager:
61
61
  }
62
62
 
63
63
  existing_tenants = collection.tenants.get()
64
- if existing_tenants:
64
+ new_tenant_names = []
65
65
 
66
- raise Exception(
67
- f"Tenants already exist in class '{collection.name}'. Update their status using ./update_tenants.py or delete them using <delete tenants> command"
68
- )
69
- else:
70
- collection.tenants.create(
71
- [
72
- Tenant(
73
- name=f"{tenant_suffix}{i}",
74
- activity_status=tenant_state_map[state],
66
+ if existing_tenants:
67
+ # Check if existing tenants follow the same suffix pattern
68
+ existing_tenant_names = list(existing_tenants.keys())
69
+ for tenant_name in existing_tenant_names:
70
+ if tenant_name.startswith(tenant_suffix):
71
+ try:
72
+ # Try to extract the index part after the suffix
73
+ int(tenant_name[len(tenant_suffix) :])
74
+ except ValueError:
75
+ raise Exception(
76
+ f"Existing tenant '{tenant_name}' does not follow the expected pattern '{tenant_suffix}N' where N is a number. "
77
+ f"Please use a different tenant_suffix or delete existing tenants."
78
+ )
79
+ else:
80
+ raise Exception(
81
+ f"Existing tenant '{tenant_name}' does not use the provided tenant_suffix '{tenant_suffix}'. "
82
+ f"Please use the same tenant_suffix as existing tenants or delete existing tenants."
75
83
  )
76
- for i in range(number_tenants)
77
- ]
78
- )
79
84
 
80
- # get_by_names is only available after 1.25.0
81
- if version.compare(semver.Version.parse("1.25.0")) < 0:
82
- tenants_list = {
83
- name: tenant
84
- for name, tenant in collection.tenants.get().items()
85
- if name.startswith(tenant_suffix)
86
- }
85
+ # Find the highest index among existing tenants
86
+ highest_index = -1
87
+ for tenant_name in existing_tenant_names:
88
+ try:
89
+ index = int(tenant_name[len(tenant_suffix) :])
90
+ highest_index = max(highest_index, index)
91
+ except ValueError:
92
+ continue
93
+
94
+ # Generate new tenant names starting from the next index
95
+ start_index = highest_index + 1
96
+ new_tenant_names = [
97
+ f"{tenant_suffix}{i}"
98
+ for i in range(start_index, start_index + number_tenants)
99
+ ]
87
100
  else:
88
- tenants_list = collection.tenants.get_by_names(
89
- [f"{tenant_suffix}{i}" for i in range(number_tenants)]
101
+ # No existing tenants, create tenants starting from index 0
102
+ new_tenant_names = [f"{tenant_suffix}{i}" for i in range(number_tenants)]
103
+
104
+ # Create the new tenants
105
+ tenants = [
106
+ Tenant(
107
+ name=name,
108
+ activity_status=tenant_state_map[state],
90
109
  )
91
- assert (
92
- len(tenants_list) == number_tenants
93
- ), f"Expected {number_tenants} tenants, but found {len(tenants_list)}"
94
- for tenant in tenants_list.values():
95
- if tenant.activity_status != tenant_state_map[state]:
110
+ for name in new_tenant_names
111
+ ]
112
+
113
+ if tenants: # Only call create if there are tenants to create
114
+ if tenant_batch_size:
115
+ for i in range(0, len(tenants), tenant_batch_size):
116
+ batch = tenants[i : i + tenant_batch_size]
117
+ collection.tenants.create(batch)
118
+ else:
119
+ collection.tenants.create(tenants)
120
+
121
+ # Verify the newly created tenants
122
+ if version.compare(semver.Version.parse("1.25.0")) < 0:
123
+ created_tenants = {
124
+ name: tenant
125
+ for name, tenant in collection.tenants.get().items()
126
+ if name in new_tenant_names
127
+ }
128
+ else:
129
+ created_tenants = collection.tenants.get_by_names(new_tenant_names)
96
130
 
131
+ # Verify all requested tenants were created
132
+ if len(created_tenants) != len(new_tenant_names):
133
+ missing_tenants = set(new_tenant_names) - set(created_tenants.keys())
97
134
  raise Exception(
98
- f"Tenant '{tenant.name}' has activity status '{tenant.activity_status}', but expected '{tenant_state_map[state]}'"
135
+ f"Not all requested tenants were created. Missing: {', '.join(missing_tenants)}"
99
136
  )
100
- click.echo(
101
- f"{len(tenants_list)} tenants added with tenant status '{tenant.activity_status}' for collection '{collection.name}'"
102
- )
137
+
138
+ # Verify tenant activity status
139
+ for tenant_name, tenant in created_tenants.items():
140
+ if tenant.activity_status != tenant_state_map[state]:
141
+ raise Exception(
142
+ f"Tenant '{tenant_name}' has activity status '{tenant.activity_status}', but expected '{tenant_state_map[state]}'"
143
+ )
144
+
145
+ click.echo(
146
+ f"{len(new_tenant_names)} tenants added with tenant status '{tenant_state_map[state]}' for collection '{collection.name}'"
147
+ )
148
+ else:
149
+ click.echo(
150
+ f"No new tenants were created for collection '{collection.name}'"
151
+ )
103
152
 
104
153
  def delete_tenants(
105
154
  self,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: weaviate-cli
3
- Version: 3.1.2
3
+ Version: 3.1.4
4
4
  Summary: Command line interface to interact with weaviate
5
5
  Home-page: https://github.com/weaviate/weaviate-cli
6
6
  Download-URL: https://github.com/weaviate/weaviate-cli
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes