weaviate-cli 3.0.2__tar.gz → 3.1.0__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 (63) hide show
  1. weaviate_cli-3.1.0/.github/dependabot.yml +13 -0
  2. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/.github/workflows/main.yaml +7 -7
  3. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/.github/workflows/release.yaml +1 -1
  4. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/PKG-INFO +33 -4
  5. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/README.md +31 -2
  6. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/cli.py +13 -2
  7. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/requirements-dev.txt +1 -1
  8. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/setup.cfg +1 -1
  9. weaviate_cli-3.1.0/test/unittests/test_managers/test_config_manager.py +141 -0
  10. weaviate_cli-3.1.0/test/unittests/test_utils.py +218 -0
  11. weaviate_cli-3.1.0/weaviate_cli/commands/assign.py +76 -0
  12. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/weaviate_cli/commands/create.py +50 -5
  13. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/weaviate_cli/commands/delete.py +32 -4
  14. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/weaviate_cli/commands/get.py +103 -5
  15. weaviate_cli-3.1.0/weaviate_cli/commands/revoke.py +75 -0
  16. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/weaviate_cli/defaults.py +46 -2
  17. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/weaviate_cli/managers/collection_manager.py +2 -0
  18. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/weaviate_cli/managers/config_manager.py +13 -6
  19. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/weaviate_cli/managers/data_manager.py +25 -19
  20. weaviate_cli-3.1.0/weaviate_cli/managers/role_manager.py +139 -0
  21. weaviate_cli-3.1.0/weaviate_cli/managers/user_manager.py +52 -0
  22. weaviate_cli-3.1.0/weaviate_cli/utils.py +214 -0
  23. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/weaviate_cli.egg-info/PKG-INFO +33 -4
  24. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/weaviate_cli.egg-info/SOURCES.txt +6 -1
  25. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/weaviate_cli.egg-info/requires.txt +1 -1
  26. weaviate_cli-3.0.2/test/unittests/test_managers/test_config_manager.py +0 -42
  27. weaviate_cli-3.0.2/test/unittests/test_utils.py +0 -97
  28. weaviate_cli-3.0.2/weaviate_cli/utils.py +0 -61
  29. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/.gitignore +0 -0
  30. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/.pre-commit-config.yaml +0 -0
  31. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/CONTRIBUTING.md +0 -0
  32. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/Dockerfile +0 -0
  33. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/LICENSE +0 -0
  34. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/MANIFEST.in +0 -0
  35. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/Makefile +0 -0
  36. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/publish.md +0 -0
  37. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/pyproject.toml +0 -0
  38. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/setup.py +0 -0
  39. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/test/README.md +0 -0
  40. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/test/__init__.py +0 -0
  41. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/test/integration/test_integration.py +0 -0
  42. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/test/unittests/conftest.py +0 -0
  43. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/test/unittests/test_cli.py +0 -0
  44. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/test/unittests/test_defaults.py +0 -0
  45. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/test/unittests/test_managers/test_collection_manager.py +0 -0
  46. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/test/unittests/test_managers/test_data_manager.py +0 -0
  47. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/test/unittests/test_managers/test_shard_manager.py +0 -0
  48. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/weaviate_cli/__init__.py +0 -0
  49. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/weaviate_cli/commands/__init__.py +0 -0
  50. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/weaviate_cli/commands/cancel.py +0 -0
  51. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/weaviate_cli/commands/query.py +0 -0
  52. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/weaviate_cli/commands/restore.py +0 -0
  53. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/weaviate_cli/commands/update.py +0 -0
  54. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/weaviate_cli/datasets/__init__.py +0 -0
  55. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/weaviate_cli/datasets/movies.json +0 -0
  56. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/weaviate_cli/managers/__init__.py +0 -0
  57. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/weaviate_cli/managers/backup_manager.py +0 -0
  58. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/weaviate_cli/managers/shard_manager.py +0 -0
  59. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/weaviate_cli/managers/tenant_manager.py +0 -0
  60. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/weaviate_cli.egg-info/dependency_links.txt +0 -0
  61. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/weaviate_cli.egg-info/entry_points.txt +0 -0
  62. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/weaviate_cli.egg-info/not-zip-safe +0 -0
  63. {weaviate_cli-3.0.2 → weaviate_cli-3.1.0}/weaviate_cli.egg-info/top_level.txt +0 -0
@@ -0,0 +1,13 @@
1
+ # Keep GitHub Actions up to date with GitHub's Dependabot...
2
+ # https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot
3
+ # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem
4
+ version: 2
5
+ updates:
6
+ - package-ecosystem: github-actions
7
+ directory: /
8
+ groups:
9
+ github-actions:
10
+ patterns:
11
+ - "*" # Group all Actions updates into a single larger pull request
12
+ schedule:
13
+ interval: weekly
@@ -29,10 +29,10 @@ jobs:
29
29
  runs-on: ubuntu-latest
30
30
  strategy:
31
31
  matrix:
32
- version: [ "3.9", "3.10", "3.11", "3.12" ]
32
+ version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ]
33
33
  steps:
34
- - uses: actions/checkout@v3
35
- - uses: actions/setup-python@v4
34
+ - uses: actions/checkout@v4
35
+ - uses: actions/setup-python@v5
36
36
  with:
37
37
  python-version: ${{ matrix.version }}
38
38
  - run: pip install -e .
@@ -42,7 +42,7 @@ jobs:
42
42
  pytest test/unittests --html=test-report-${{ matrix.version }}.html --self-contained-html
43
43
  - name: Upload test results
44
44
  if: always()
45
- uses: actions/upload-artifact@v3
45
+ uses: actions/upload-artifact@v4
46
46
  with:
47
47
  name: test-results-${{ matrix.version }}
48
48
  path: test-report-${{ matrix.version }}.html
@@ -56,10 +56,10 @@ jobs:
56
56
  runs-on: ubuntu-latest
57
57
  strategy:
58
58
  matrix:
59
- version: [ "3.9", "3.10", "3.11", "3.12" ]
59
+ version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ]
60
60
  steps:
61
- - uses: actions/checkout@v3
62
- - uses: actions/setup-python@v4
61
+ - uses: actions/checkout@v4
62
+ - uses: actions/setup-python@v5
63
63
  with:
64
64
  python-version: ${{ matrix.version }}
65
65
  - run: pip install -e .
@@ -15,7 +15,7 @@ jobs:
15
15
  with:
16
16
  fetch-depth: 0
17
17
  - name: Set up Python 3.11
18
- uses: actions/setup-python@v4
18
+ uses: actions/setup-python@v5
19
19
  with:
20
20
  python-version: "3.11"
21
21
  - name: Install dependencies
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: weaviate-cli
3
- Version: 3.0.2
3
+ Version: 3.1.0
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
@@ -28,7 +28,7 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
28
28
  Requires-Python: >=3.9
29
29
  Description-Content-Type: text/markdown
30
30
  License-File: LICENSE
31
- Requires-Dist: weaviate-client>=4.5.0
31
+ Requires-Dist: weaviate-client>=4.10.0
32
32
  Requires-Dist: click==8.1.7
33
33
  Requires-Dist: semver>=3.0.2
34
34
  Requires-Dist: numpy>=1.24.0
@@ -50,6 +50,7 @@ A powerful command-line interface for managing and interacting with Weaviate vec
50
50
  - **Backup & Restore**: Create and restore backups with support for S3, GCS and filesystem
51
51
  - **Sharding**: Monitor and manage collection shards
52
52
  - **Flexible Configuration**: Configure vector indexes, replication, consistency levels and more
53
+ - **Role Management**: Assign and revoke roles and permissions to users
53
54
 
54
55
  ## Quick Start
55
56
  Install using pip:
@@ -58,6 +59,11 @@ Install using pip:
58
59
  pip install weaviate-cli
59
60
  ```
60
61
 
62
+ On Mac, install using Homebrew:
63
+ ```bash
64
+ brew install weaviate-cli
65
+ ```
66
+
61
67
  ## Basic Usage
62
68
 
63
69
  ```bash
@@ -82,7 +88,8 @@ weaviate-cli query data --collection movies --search-type hybrid --query "action
82
88
  - **get**: Retrieve collection info, tenant details or shard status
83
89
  - **query**: Search data using various methods
84
90
  - **restore**: Restore backups from supported backends
85
-
91
+ - **assign**: Assign roles and permissions to users
92
+ - **revoke**: Revoke roles and permissions from users
86
93
  ## Configuration
87
94
 
88
95
  Weaviate CLI allows you to configure your cluster endpoints and parameters through a configuration file. By default, the CLI looks for a
@@ -134,9 +141,30 @@ Here you can see an example on how the configuration file should look like if yo
134
141
  }
135
142
  ```
136
143
 
144
+ If you want to allow using different users for different actions in your cluster, you can specify the different users in the configuration file and use the `--user` option to specify which user to use for a specific action.
145
+ An example of how the configuration file should look like is the following:
146
+
147
+ ```json
148
+ {
149
+ "host": "your-weaviate-host",
150
+ "auth": {
151
+ "type": "user",
152
+ "user1": "your-api-key-for-user1",
153
+ "user2": "your-api-key-for-user2"
154
+ }
155
+ }
156
+ ```
157
+ It's important to note that the "type" key must be set to "user" and the users must be specified in the auth section.
158
+ When using the `weaviate-cli` command, you can specify the user to use for an action by using the `--user` option. For example:
159
+
160
+ ```bash
161
+ weaviate-cli --user user1 create collection --collection movies --vectorizer transformers
162
+ weaviate-cli --user user2 get collection --collection movies
163
+ ```
164
+
137
165
  ## Requirements
138
166
 
139
- - Python 3.9+
167
+ - Python 3.10+
140
168
  - Weaviate instance (local or remote)
141
169
 
142
170
  ## Documentation
@@ -145,6 +173,7 @@ Detailed documentation will be added soon.
145
173
 
146
174
  ## Supported Model Provider
147
175
 
176
+ - Weaviate Embeddings Service (WCD clusters only)
148
177
  - Contextionary
149
178
  - Transformers
150
179
  - OpenAI
@@ -14,6 +14,7 @@ A powerful command-line interface for managing and interacting with Weaviate vec
14
14
  - **Backup & Restore**: Create and restore backups with support for S3, GCS and filesystem
15
15
  - **Sharding**: Monitor and manage collection shards
16
16
  - **Flexible Configuration**: Configure vector indexes, replication, consistency levels and more
17
+ - **Role Management**: Assign and revoke roles and permissions to users
17
18
 
18
19
  ## Quick Start
19
20
  Install using pip:
@@ -22,6 +23,11 @@ Install using pip:
22
23
  pip install weaviate-cli
23
24
  ```
24
25
 
26
+ On Mac, install using Homebrew:
27
+ ```bash
28
+ brew install weaviate-cli
29
+ ```
30
+
25
31
  ## Basic Usage
26
32
 
27
33
  ```bash
@@ -46,7 +52,8 @@ weaviate-cli query data --collection movies --search-type hybrid --query "action
46
52
  - **get**: Retrieve collection info, tenant details or shard status
47
53
  - **query**: Search data using various methods
48
54
  - **restore**: Restore backups from supported backends
49
-
55
+ - **assign**: Assign roles and permissions to users
56
+ - **revoke**: Revoke roles and permissions from users
50
57
  ## Configuration
51
58
 
52
59
  Weaviate CLI allows you to configure your cluster endpoints and parameters through a configuration file. By default, the CLI looks for a
@@ -98,9 +105,30 @@ Here you can see an example on how the configuration file should look like if yo
98
105
  }
99
106
  ```
100
107
 
108
+ If you want to allow using different users for different actions in your cluster, you can specify the different users in the configuration file and use the `--user` option to specify which user to use for a specific action.
109
+ An example of how the configuration file should look like is the following:
110
+
111
+ ```json
112
+ {
113
+ "host": "your-weaviate-host",
114
+ "auth": {
115
+ "type": "user",
116
+ "user1": "your-api-key-for-user1",
117
+ "user2": "your-api-key-for-user2"
118
+ }
119
+ }
120
+ ```
121
+ It's important to note that the "type" key must be set to "user" and the users must be specified in the auth section.
122
+ When using the `weaviate-cli` command, you can specify the user to use for an action by using the `--user` option. For example:
123
+
124
+ ```bash
125
+ weaviate-cli --user user1 create collection --collection movies --vectorizer transformers
126
+ weaviate-cli --user user2 get collection --collection movies
127
+ ```
128
+
101
129
  ## Requirements
102
130
 
103
- - Python 3.9+
131
+ - Python 3.10+
104
132
  - Weaviate instance (local or remote)
105
133
 
106
134
  ## Documentation
@@ -109,6 +137,7 @@ Detailed documentation will be added soon.
109
137
 
110
138
  ## Supported Model Provider
111
139
 
140
+ - Weaviate Embeddings Service (WCD clusters only)
112
141
  - Contextionary
113
142
  - Transformers
114
143
  - OpenAI
@@ -1,3 +1,4 @@
1
+ from typing import Optional
1
2
  import click
2
3
  import sys
3
4
  from weaviate_cli.managers.config_manager import ConfigManager
@@ -8,6 +9,8 @@ from weaviate_cli.commands.update import update
8
9
  from weaviate_cli.commands.query import query
9
10
  from weaviate_cli.commands.restore import restore
10
11
  from weaviate_cli.commands.cancel import cancel
12
+ from weaviate_cli.commands.assign import assign
13
+ from weaviate_cli.commands.revoke import revoke
11
14
  from weaviate_cli import __version__
12
15
 
13
16
 
@@ -26,6 +29,12 @@ def print_version(ctx, param, value):
26
29
  is_flag=False,
27
30
  help="If specified cli uses the config specified with this path.",
28
31
  )
32
+ @click.option(
33
+ "--user",
34
+ required=False,
35
+ type=str,
36
+ help="If specified cli uses the user specified in the config file.",
37
+ )
29
38
  @click.option(
30
39
  "--version",
31
40
  is_flag=True,
@@ -35,10 +44,10 @@ def print_version(ctx, param, value):
35
44
  help="Prints the version of the CLI.",
36
45
  )
37
46
  @click.pass_context
38
- def main(ctx: click.Context, config_file):
47
+ def main(ctx: click.Context, config_file: Optional[str], user: Optional[str]):
39
48
  """Weaviate CLI tool"""
40
49
  try:
41
- ctx.obj = {"config": ConfigManager(config_file)}
50
+ ctx.obj = {"config": ConfigManager(config_file, user)}
42
51
  except Exception as e:
43
52
  click.echo(f"Fatal Error: {e}")
44
53
  sys.exit(1)
@@ -51,6 +60,8 @@ main.add_command(update)
51
60
  main.add_command(restore)
52
61
  main.add_command(query)
53
62
  main.add_command(cancel)
63
+ main.add_command(assign)
64
+ main.add_command(revoke)
54
65
 
55
66
  if __name__ == "__main__":
56
67
  main()
@@ -1,4 +1,4 @@
1
- weaviate-client>=4.0.0
1
+ weaviate-client>=4.10.0
2
2
  click==8.1.7
3
3
  twine
4
4
  pytest
@@ -36,7 +36,7 @@ classifiers =
36
36
  include_package_data = True
37
37
  python_requires = >=3.9
38
38
  install_requires =
39
- weaviate-client>=4.5.0
39
+ weaviate-client>=4.10.0
40
40
  click==8.1.7
41
41
  semver>=3.0.2
42
42
  numpy>=1.24.0
@@ -0,0 +1,141 @@
1
+ import socket
2
+ import pytest
3
+ import weaviate
4
+ from unittest.mock import MagicMock, mock_open, patch
5
+ from weaviate_cli.managers.config_manager import ConfigManager
6
+ import json
7
+
8
+
9
+ def test_init_with_config_file():
10
+ config_data = {"host": "localhost", "http_port": "8080", "grpc_port": "50051"}
11
+
12
+ with patch("builtins.open", mock_open(read_data=json.dumps(config_data))):
13
+ with patch("os.path.isfile") as mock_isfile:
14
+ mock_isfile.return_value = True
15
+ config = ConfigManager(config_file="test_config.json")
16
+ assert config.config == config_data
17
+
18
+
19
+ def test_create_default_config():
20
+ with patch("pathlib.Path.exists") as mock_exists:
21
+ mock_exists.return_value = False
22
+ config = ConfigManager()
23
+ assert config.config["host"] == "localhost"
24
+ assert config.config["http_port"] == "8080"
25
+ assert config.config["grpc_port"] == "50051"
26
+
27
+
28
+ @patch("socket.create_connection")
29
+ def test_check_host_docker_internal(mock_socket):
30
+ config = ConfigManager()
31
+
32
+ # Test successful connection
33
+ mock_socket.return_value = MagicMock()
34
+ assert config._ConfigManager__check_host_docker_internal() == True
35
+
36
+
37
+ @patch("socket.create_connection")
38
+ def test_check_host_docker_internal_failed(mock_socket):
39
+ config = ConfigManager()
40
+
41
+ # Test failed connection
42
+ mock_socket.side_effect = socket.error()
43
+ assert config._ConfigManager__check_host_docker_internal() == False
44
+
45
+
46
+ def test_init_with_user():
47
+ config_data = {
48
+ "host": "localhost",
49
+ "http_port": "8080",
50
+ "grpc_port": "50051",
51
+ "auth": {"type": "user", "admin": "admin-key", "jose": "jose-key"},
52
+ }
53
+
54
+ with patch("builtins.open", mock_open(read_data=json.dumps(config_data))):
55
+ with patch("os.path.isfile") as mock_isfile:
56
+ mock_isfile.return_value = True
57
+ config = ConfigManager(config_file="test_config.json", user="jose")
58
+ assert config.user == "jose"
59
+ assert config.config == config_data
60
+
61
+
62
+ def test_get_client_with_user_auth():
63
+ config_data = {
64
+ "host": "localhost",
65
+ "http_port": "8080",
66
+ "grpc_port": "50051",
67
+ "auth": {"type": "user", "admin": "admin-key", "jose": "jose-key"},
68
+ }
69
+
70
+ with patch("builtins.open", mock_open(read_data=json.dumps(config_data))):
71
+ with patch("os.path.isfile") as mock_isfile:
72
+ mock_isfile.return_value = True
73
+ with patch("weaviate.connect_to_local") as mock_connect:
74
+ config = ConfigManager(config_file="test_config.json", user="jose")
75
+ config.get_client()
76
+
77
+ mock_connect.assert_called_once()
78
+ call_kwargs = mock_connect.call_args.kwargs
79
+ assert isinstance(
80
+ call_kwargs["auth_credentials"], weaviate.auth.AuthApiKey
81
+ )
82
+ assert call_kwargs["auth_credentials"].api_key == "jose-key"
83
+
84
+
85
+ def test_get_client_with_invalid_user():
86
+ config_data = {
87
+ "host": "localhost",
88
+ "http_port": "8080",
89
+ "grpc_port": "50051",
90
+ "auth": {"type": "user", "admin": "admin-key"},
91
+ }
92
+
93
+ with patch("builtins.open", mock_open(read_data=json.dumps(config_data))):
94
+ with patch("os.path.isfile") as mock_isfile:
95
+ mock_isfile.return_value = True
96
+ config = ConfigManager(config_file="test_config.json", user="jose")
97
+ with pytest.raises(Exception) as exc_info:
98
+ config.get_client()
99
+ assert str(exc_info.value) == "User 'jose' not found in config file"
100
+
101
+
102
+ def test_get_client_missing_user():
103
+ config_data = {
104
+ "host": "localhost",
105
+ "http_port": "8080",
106
+ "grpc_port": "50051",
107
+ "auth": {"type": "user", "admin": "admin-key"},
108
+ }
109
+
110
+ with patch("builtins.open", mock_open(read_data=json.dumps(config_data))):
111
+ with patch("os.path.isfile") as mock_isfile:
112
+ mock_isfile.return_value = True
113
+ config = ConfigManager(config_file="test_config.json")
114
+ with pytest.raises(Exception) as exc_info:
115
+ config.get_client()
116
+ assert (
117
+ str(exc_info.value) == "User must be specified when auth type is 'user'"
118
+ )
119
+
120
+
121
+ def test_get_client_with_api_key_auth():
122
+ config_data = {
123
+ "host": "localhost",
124
+ "http_port": "8080",
125
+ "grpc_port": "50051",
126
+ "auth": {"type": "api_key", "api_key": "test-key"},
127
+ }
128
+
129
+ with patch("builtins.open", mock_open(read_data=json.dumps(config_data))):
130
+ with patch("os.path.isfile") as mock_isfile:
131
+ mock_isfile.return_value = True
132
+ with patch("weaviate.connect_to_local") as mock_connect:
133
+ config = ConfigManager(config_file="test_config.json")
134
+ config.get_client()
135
+
136
+ mock_connect.assert_called_once()
137
+ call_kwargs = mock_connect.call_args.kwargs
138
+ assert isinstance(
139
+ call_kwargs["auth_credentials"], weaviate.auth.AuthApiKey
140
+ )
141
+ assert call_kwargs["auth_credentials"].api_key == "test-key"
@@ -0,0 +1,218 @@
1
+ import pytest
2
+ from unittest.mock import MagicMock
3
+ from weaviate_cli.utils import (
4
+ get_client_from_context,
5
+ get_random_string,
6
+ pp_objects,
7
+ parse_permission,
8
+ )
9
+ from weaviate.collections import Collection
10
+ from io import StringIO
11
+ import sys
12
+ from weaviate.rbac.models import Permissions
13
+
14
+
15
+ def test_get_client_from_context(mock_click_context, mock_client):
16
+ mock_click_context.obj["config"].get_client.return_value = mock_client
17
+ client = get_client_from_context(mock_click_context)
18
+ assert client == mock_client
19
+ mock_click_context.obj["config"].get_client.assert_called_once()
20
+
21
+
22
+ def test_get_random_string():
23
+ # Test different lengths
24
+ assert len(get_random_string(5)) == 5
25
+ assert len(get_random_string(10)) == 10
26
+
27
+ # Test randomness
28
+ str1 = get_random_string(8)
29
+ str2 = get_random_string(8)
30
+ assert str1 != str2
31
+
32
+ # Test only lowercase letters
33
+ random_str = get_random_string(20)
34
+ assert all(c.islower() for c in random_str)
35
+
36
+
37
+ def test_pp_objects_empty():
38
+ # Test empty response
39
+ response = MagicMock()
40
+ response.objects = []
41
+
42
+ # Capture stdout
43
+ captured_output = StringIO()
44
+ sys.stdout = captured_output
45
+
46
+ pp_objects(response, ["name", "description"])
47
+
48
+ sys.stdout = sys.__stdout__
49
+ assert "No objects found" in captured_output.getvalue()
50
+
51
+
52
+ def test_pp_objects_with_data():
53
+ # Mock response object
54
+ response = MagicMock()
55
+ mock_obj = MagicMock()
56
+ mock_obj.uuid = "test-uuid-1234"
57
+ mock_obj.properties = {"name": "test_name", "description": "test_description"}
58
+ mock_obj.metadata.distance = 0.5
59
+ mock_obj.metadata.certainty = 0.8
60
+ mock_obj.metadata.score = 0.9
61
+ response.objects = [mock_obj]
62
+
63
+ # Capture stdout
64
+ captured_output = StringIO()
65
+ sys.stdout = captured_output
66
+
67
+ pp_objects(response, ["name", "description"])
68
+
69
+ sys.stdout = sys.__stdout__
70
+ output = captured_output.getvalue()
71
+
72
+ # Verify output contains expected data
73
+ assert "test-uuid-1234" in output
74
+ assert "test_name" in output
75
+ assert "test_description" in output
76
+ assert "0.5" in output
77
+ assert "0.8" in output
78
+ assert "0.9" in output
79
+ assert "Total: 1 objects" in output
80
+
81
+
82
+ def test_pp_objects_missing_properties():
83
+ # Test handling of missing properties
84
+ response = MagicMock()
85
+ mock_obj = MagicMock()
86
+ mock_obj.uuid = "test-uuid-5678"
87
+ mock_obj.properties = {"name": "test_name"} # Missing description
88
+ mock_obj.metadata.distance = None
89
+ mock_obj.metadata.certainty = None
90
+ mock_obj.metadata.score = None
91
+ response.objects = [mock_obj]
92
+
93
+ captured_output = StringIO()
94
+ sys.stdout = captured_output
95
+
96
+ pp_objects(response, ["name", "description"])
97
+
98
+ sys.stdout = sys.__stdout__
99
+ output = captured_output.getvalue()
100
+
101
+ assert "test-uuid-5678" in output
102
+ assert "test_name" in output
103
+ assert "None" in output
104
+
105
+
106
+ def test_parse_permission_collections():
107
+ # Test basic collection permissions
108
+ assert parse_permission("read_collections:Movies") == Permissions.collections(
109
+ collection="Movies", read_config=True
110
+ )
111
+
112
+ # Test multiple collections
113
+ assert parse_permission(
114
+ "create_collections:Movies,Books,Films"
115
+ ) == Permissions.collections(
116
+ collection=["Movies", "Books", "Films"], create_collection=True
117
+ )
118
+
119
+ # Test crud collections
120
+ assert parse_permission("crud_collections:Movies") == Permissions.collections(
121
+ collection="Movies",
122
+ create_collection=True,
123
+ read_config=True,
124
+ update_config=True,
125
+ delete_collection=True,
126
+ )
127
+
128
+
129
+ def test_parse_permission_data():
130
+ # Test basic data permissions
131
+ assert parse_permission("read_data:Movies") == Permissions.data(
132
+ collection="Movies", read=True
133
+ )
134
+
135
+ # Test multiple collections
136
+ assert parse_permission("create_data:Movies,Books,Films") == Permissions.data(
137
+ collection=["Movies", "Books", "Films"], create=True
138
+ )
139
+
140
+ # Test crud data
141
+ assert parse_permission("crud_data:Movies") == Permissions.data(
142
+ collection="Movies", create=True, read=True, update=True, delete=True
143
+ )
144
+
145
+
146
+ def test_parse_permission_roles():
147
+ # Test basic role permissions
148
+ assert parse_permission("read_roles:custom") == Permissions.roles(
149
+ role="custom", read=True
150
+ )
151
+
152
+ # Test multiple roles
153
+ assert parse_permission("read_roles:custom,editor,viewer") == Permissions.roles(
154
+ role=["custom", "editor", "viewer"], read=True
155
+ )
156
+
157
+ # Test manage roles
158
+ assert parse_permission("manage_roles:custom") == Permissions.roles(
159
+ role="custom", manage=True
160
+ )
161
+
162
+
163
+ def test_parse_permission_backups():
164
+ # Test basic backup permissions
165
+ assert parse_permission("manage_backups:Movies") == Permissions.backup(
166
+ collection="Movies", manage=True
167
+ )
168
+
169
+ # Test multiple collections
170
+ assert parse_permission("manage_backups:Movies,Books,Films") == Permissions.backup(
171
+ collection=["Movies", "Books", "Films"], manage=True
172
+ )
173
+
174
+
175
+ def test_parse_permission_nodes():
176
+ # Test basic node permissions
177
+ assert parse_permission("read_nodes:minimal:Movies") == Permissions.nodes(
178
+ collection="Movies", verbosity="minimal", read=True
179
+ )
180
+
181
+ # Test verbose node permissions
182
+ assert parse_permission("read_nodes:verbose:Movies") == Permissions.nodes(
183
+ collection="Movies", verbosity="verbose", read=True
184
+ )
185
+
186
+ # Test multiple collections
187
+ assert parse_permission(
188
+ "read_nodes:verbose:Movies,Books,Films"
189
+ ) == Permissions.nodes(
190
+ collection=["Movies", "Books", "Films"], verbosity="verbose", read=True
191
+ )
192
+
193
+
194
+ def test_parse_permission_cluster():
195
+ # Test cluster read permission
196
+ assert parse_permission("read_cluster") == Permissions.cluster(read=True)
197
+
198
+
199
+ def test_parse_permission_invalid():
200
+ # Test invalid action
201
+ with pytest.raises(ValueError, match="Invalid resource type: action"):
202
+ parse_permission("invalid_action:Movies")
203
+
204
+ # Test invalid crud combination
205
+ with pytest.raises(ValueError, match="Invalid crud combination: xyz"):
206
+ parse_permission("xyz_collections:Movies")
207
+
208
+ # Test invalid node verbosity
209
+ with pytest.raises(ValueError, match="Input should be 'minimal' or 'verbose'"):
210
+ parse_permission("read_nodes:invalid:Movies")
211
+
212
+ # Test missing node verbosity
213
+ with pytest.raises(ValueError, match="Input should be 'minimal' or 'verbose'"):
214
+ parse_permission("read_nodes:Movies")
215
+
216
+ # Test passing too many parts
217
+ with pytest.raises(ValueError, match="Invalid permission format"):
218
+ parse_permission("read_nodes:minimal:Movies:custom")