weaviate-cli 3.1.2__tar.gz → 3.1.3__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.3}/PKG-INFO +1 -1
  2. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli/commands/create.py +25 -2
  3. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli/commands/delete.py +8 -2
  4. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli/commands/update.py +1 -1
  5. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli/defaults.py +7 -4
  6. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli/managers/collection_manager.py +1 -1
  7. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli/managers/data_manager.py +65 -24
  8. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli/managers/tenant_manager.py +83 -34
  9. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli.egg-info/PKG-INFO +1 -1
  10. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/.github/dependabot.yml +0 -0
  11. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/.github/workflows/main.yaml +0 -0
  12. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/.github/workflows/release.yaml +0 -0
  13. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/.gitignore +0 -0
  14. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/.pre-commit-config.yaml +0 -0
  15. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/CONTRIBUTING.md +0 -0
  16. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/Dockerfile +0 -0
  17. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/LICENSE +0 -0
  18. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/MANIFEST.in +0 -0
  19. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/Makefile +0 -0
  20. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/README.md +0 -0
  21. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/cli.py +0 -0
  22. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/publish.md +0 -0
  23. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/pyproject.toml +0 -0
  24. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/requirements-dev.txt +0 -0
  25. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/setup.cfg +0 -0
  26. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/setup.py +0 -0
  27. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/test/README.md +0 -0
  28. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/test/__init__.py +0 -0
  29. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/test/integration/test_integration.py +0 -0
  30. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/test/unittests/conftest.py +0 -0
  31. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/test/unittests/test_cli.py +0 -0
  32. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/test/unittests/test_defaults.py +0 -0
  33. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/test/unittests/test_managers/test_collection_manager.py +0 -0
  34. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/test/unittests/test_managers/test_config_manager.py +0 -0
  35. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/test/unittests/test_managers/test_data_manager.py +0 -0
  36. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/test/unittests/test_managers/test_node_manager.py +0 -0
  37. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/test/unittests/test_managers/test_shard_manager.py +0 -0
  38. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/test/unittests/test_utils.py +0 -0
  39. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli/__init__.py +0 -0
  40. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli/commands/__init__.py +0 -0
  41. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli/commands/assign.py +0 -0
  42. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli/commands/cancel.py +0 -0
  43. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli/commands/get.py +0 -0
  44. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli/commands/query.py +0 -0
  45. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli/commands/restore.py +0 -0
  46. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli/commands/revoke.py +0 -0
  47. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli/completion/__init__.py +0 -0
  48. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli/completion/complete.py +0 -0
  49. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli/datasets/__init__.py +0 -0
  50. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli/datasets/movies.json +0 -0
  51. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli/managers/__init__.py +0 -0
  52. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli/managers/backup_manager.py +0 -0
  53. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli/managers/config_manager.py +0 -0
  54. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli/managers/node_manager.py +0 -0
  55. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli/managers/role_manager.py +0 -0
  56. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli/managers/shard_manager.py +0 -0
  57. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli/managers/user_manager.py +0 -0
  58. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli/types/models.py +0 -0
  59. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli/utils.py +0 -0
  60. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli.egg-info/SOURCES.txt +0 -0
  61. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli.egg-info/dependency_links.txt +0 -0
  62. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli.egg-info/entry_points.txt +0 -0
  63. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli.egg-info/not-zip-safe +0 -0
  64. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/weaviate_cli.egg-info/requires.txt +0 -0
  65. {weaviate_cli-3.1.2 → weaviate_cli-3.1.3}/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.3
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,
@@ -319,6 +338,8 @@ def create_data_cli(
319
338
  consistency_level,
320
339
  randomize,
321
340
  auto_tenants,
341
+ tenants,
342
+ tenant_suffix,
322
343
  vector_dimensions,
323
344
  uuid,
324
345
  ):
@@ -349,8 +370,10 @@ def create_data_cli(
349
370
  consistency_level=consistency_level,
350
371
  randomize=randomize,
351
372
  auto_tenants=auto_tenants,
373
+ tenants_list=tenants.split(",") if tenants else None,
352
374
  vector_dimensions=vector_dimensions,
353
375
  uuid=uuid,
376
+ tenant_suffix=tenant_suffix,
354
377
  )
355
378
  except Exception as e:
356
379
  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
 
@@ -97,7 +100,7 @@ class DeleteCollectionDefaults:
97
100
  @dataclass
98
101
  class DeleteTenantsDefaults:
99
102
  collection: str = "Movies"
100
- tenant_suffix: str = "Tenant--"
103
+ tenant_suffix: str = "Tenant-"
101
104
  number_tenants: int = 100
102
105
 
103
106
 
@@ -183,7 +186,7 @@ class UpdateCollectionDefaults:
183
186
  @dataclass
184
187
  class UpdateTenantsDefaults:
185
188
  collection: str = "Movies"
186
- tenant_suffix: str = "Tenant--"
189
+ tenant_suffix: str = "Tenant-"
187
190
  number_tenants: int = 100
188
191
  state: str = "active"
189
192
 
@@ -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,7 +207,9 @@ 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,
@@ -217,14 +223,19 @@ class DataManager:
217
223
 
218
224
  col: Collection = self.client.collections.get(collection)
219
225
  try:
220
- tenants = [key for key in col.tenants.get().keys()]
226
+ existing_tenants = [key for key in col.tenants.get().keys()]
221
227
  except Exception as e:
222
228
  # Check if the error is due to multi-tenancy being disabled
223
229
  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"]
230
+ if (
231
+ tenants_list is not None
232
+ or auto_tenants != CreateDataDefaults.auto_tenants
233
+ ):
234
+ raise Exception(
235
+ f"Collection '{col.name}' does not have multi-tenancy enabled. Adding data to tenants is not possible."
236
+ )
237
+
238
+ existing_tenants = ["None"]
228
239
  else:
229
240
  raise e
230
241
  if (
@@ -241,16 +252,23 @@ class DataManager:
241
252
  "all": wvc.ConsistencyLevel.ALL,
242
253
  "one": wvc.ConsistencyLevel.ONE,
243
254
  }
255
+ tenants = existing_tenants
244
256
  if auto_tenants > 0:
245
- if tenants == "None":
246
- tenants = [f"Tenant--{i}" for i in range(1, auto_tenants + 1)]
257
+ if tenants_list is not None:
258
+ raise Exception(
259
+ f"Either --tenants or --auto_tenants must be provided, not both."
260
+ )
261
+ if existing_tenants == "None":
262
+ tenants = [f"{tenant_suffix}{i}" for i in range(1, auto_tenants + 1)]
247
263
  else:
248
- if len(tenants) < auto_tenants:
264
+ if len(existing_tenants) < auto_tenants:
249
265
  tenants += [
250
- f"Tenant--{i}"
251
- for i in range(len(tenants) + 1, auto_tenants + 1)
266
+ f"{tenant_suffix}{i}"
267
+ for i in range(len(existing_tenants) + 1, auto_tenants + 1)
252
268
  ]
253
-
269
+ else:
270
+ if tenants_list is not None:
271
+ tenants = tenants_list
254
272
  for tenant in tenants:
255
273
  if tenant == "None":
256
274
  collection = self.__ingest_data(
@@ -273,7 +291,7 @@ class DataManager:
273
291
  uuid,
274
292
  named_vectors,
275
293
  )
276
-
294
+ collection.batch.wait_for_vector_indexing()
277
295
  if len(collection) != limit:
278
296
  click.echo(
279
297
  f"Error occurred while ingesting data for tenant '{tenant}'. Check number of objects inserted."
@@ -402,25 +420,43 @@ class DataManager:
402
420
  print(f"Object deleted: {uuid} into class '{collection.name}'")
403
421
  return 1
404
422
 
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"
423
+ # Calculate the number of full batches and handle any remaining objects
424
+ # Use math.ceil to ensure we process all objects, even if num_objects < MAX_OBJECTS_PER_BATCH
425
+ iterations = math.ceil(num_objects / MAX_OBJECTS_PER_BATCH)
426
+ deleted_objects = 0
427
+
428
+ for _ in range(iterations):
429
+ # Determine how many objects to fetch in this batch
430
+ batch_size = min(MAX_OBJECTS_PER_BATCH, num_objects - deleted_objects)
431
+ if batch_size <= 0:
432
+ break
433
+
434
+ res = collection.query.fetch_objects(limit=batch_size)
435
+ if len(res.objects) == 0:
436
+ click.echo(
437
+ f"No objects found in class '{collection.name}'. Insert objects first using <ingest data> command"
438
+ )
439
+ return deleted_objects
440
+
441
+ ids = [o.uuid for o in res.objects]
442
+ collection.with_consistency_level(cl).data.delete_many(
443
+ where=Filter.by_id().contains_any(ids)
409
444
  )
410
- return -1
411
- data_objects = res.objects
445
+ deleted_objects += len(ids)
412
446
 
413
- for obj in data_objects:
414
- collection.with_consistency_level(cl).data.delete_by_id(uuid=obj.uuid)
447
+ # If we've deleted fewer objects than expected, there might not be any more objects
448
+ if len(ids) < batch_size:
449
+ break
415
450
 
416
- print(f"Deleted {num_objects} objects into class '{collection.name}'")
417
- return num_objects
451
+ print(f"Deleted {deleted_objects} objects from class '{collection.name}'")
452
+ return deleted_objects
418
453
 
419
454
  def delete_data(
420
455
  self,
421
456
  collection: str = DeleteDataDefaults.collection,
422
457
  limit: int = DeleteDataDefaults.limit,
423
458
  consistency_level: str = DeleteDataDefaults.consistency_level,
459
+ tenants_list: Optional[List[str]] = None,
424
460
  uuid: Optional[str] = DeleteDataDefaults.uuid,
425
461
  ) -> None:
426
462
 
@@ -433,14 +469,14 @@ class DataManager:
433
469
 
434
470
  col: Collection = self.client.collections.get(collection)
435
471
  try:
436
- tenants = [key for key in col.tenants.get().keys()]
472
+ existing_tenants = [key for key in col.tenants.get().keys()]
437
473
  except Exception as e:
438
474
  # Check if the error is due to multi-tenancy being disabled
439
475
  if "multi-tenancy is not enabled" in str(e):
440
476
  click.echo(
441
477
  f"Collection '{col.name}' does not have multi-tenancy enabled. Skipping tenant information collection."
442
478
  )
443
- tenants = ["None"]
479
+ existing_tenants = ["None"]
444
480
 
445
481
  cl_map = {
446
482
  "quorum": wvc.ConsistencyLevel.QUORUM,
@@ -448,6 +484,11 @@ class DataManager:
448
484
  "one": wvc.ConsistencyLevel.ONE,
449
485
  }
450
486
 
487
+ if tenants_list is not None:
488
+ tenants = tenants_list
489
+ else:
490
+ tenants = existing_tenants
491
+
451
492
  for tenant in tenants:
452
493
  if tenant == "None":
453
494
  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.3
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