weaviate-ruby 0.9.2 → 0.10.0
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.
- checksums.yaml +4 -4
- data/.standard.yml +1 -0
- data/CHANGELOG.md +31 -0
- data/Gemfile +8 -2
- data/Gemfile.lock +56 -68
- data/README.md +220 -29
- data/Rakefile +2 -0
- data/lib/weaviate/aliases.rb +40 -0
- data/lib/weaviate/backups.rb +61 -6
- data/lib/weaviate/classifications.rb +1 -3
- data/lib/weaviate/client.rb +88 -40
- data/lib/weaviate/{schema.rb → collections.rb} +84 -51
- data/lib/weaviate/connection.rb +147 -0
- data/lib/weaviate/error.rb +52 -0
- data/lib/weaviate/graphql.rb +19 -0
- data/lib/weaviate/groups.rb +39 -0
- data/lib/weaviate/health.rb +13 -4
- data/lib/weaviate/nodes.rb +10 -2
- data/lib/weaviate/objects.rb +101 -11
- data/lib/weaviate/query.rb +158 -84
- data/lib/weaviate/roles.rb +65 -0
- data/lib/weaviate/users.rb +94 -0
- data/lib/weaviate/version.rb +1 -1
- data/lib/weaviate.rb +9 -2
- data/sig/weaviate.rbs +419 -1
- metadata +19 -41
data/lib/weaviate/backups.rb
CHANGED
|
@@ -4,17 +4,29 @@ module Weaviate
|
|
|
4
4
|
class Backups < Base
|
|
5
5
|
PATH = "backups"
|
|
6
6
|
|
|
7
|
+
# List backups on a backend.
|
|
8
|
+
# @param order [String] "asc" or "desc" (default) by start time
|
|
9
|
+
def list(backend:, order: nil)
|
|
10
|
+
response = client.connection.get("#{PATH}/#{backend}") do |req|
|
|
11
|
+
req.params["order"] = order unless order.nil?
|
|
12
|
+
end
|
|
13
|
+
response.body
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @param config [Hash] Backup configuration, e.g. { Bucket: "my-bucket", Path: "backups", CPUPercentage: 80 }
|
|
7
17
|
def create(
|
|
8
18
|
backend:,
|
|
9
19
|
id:,
|
|
10
20
|
include: nil,
|
|
11
|
-
exclude: nil
|
|
21
|
+
exclude: nil,
|
|
22
|
+
config: nil
|
|
12
23
|
)
|
|
13
24
|
response = client.connection.post("#{PATH}/#{backend}") do |req|
|
|
14
25
|
req.body = {}
|
|
15
26
|
req.body["id"] = id
|
|
16
27
|
req.body["include"] = include if include
|
|
17
28
|
req.body["exclude"] = exclude if exclude
|
|
29
|
+
req.body["config"] = config if config
|
|
18
30
|
end
|
|
19
31
|
|
|
20
32
|
response.body
|
|
@@ -22,22 +34,41 @@ module Weaviate
|
|
|
22
34
|
|
|
23
35
|
def get(
|
|
24
36
|
backend:,
|
|
25
|
-
id
|
|
37
|
+
id:,
|
|
38
|
+
bucket: nil,
|
|
39
|
+
path: nil
|
|
26
40
|
)
|
|
27
|
-
response = client.connection.get("#{PATH}/#{backend}/#{id}")
|
|
41
|
+
response = client.connection.get("#{PATH}/#{backend}/#{id}") do |req|
|
|
42
|
+
location_params(req, bucket, path)
|
|
43
|
+
end
|
|
28
44
|
response.body
|
|
29
45
|
end
|
|
30
46
|
|
|
47
|
+
# Cancel a backup that is in progress.
|
|
48
|
+
def cancel(
|
|
49
|
+
backend:,
|
|
50
|
+
id:,
|
|
51
|
+
bucket: nil,
|
|
52
|
+
path: nil
|
|
53
|
+
)
|
|
54
|
+
response = client.connection.delete("#{PATH}/#{backend}/#{id}") do |req|
|
|
55
|
+
location_params(req, bucket, path)
|
|
56
|
+
end
|
|
57
|
+
response.success?
|
|
58
|
+
end
|
|
59
|
+
|
|
31
60
|
def restore(
|
|
32
61
|
backend:,
|
|
33
62
|
id:,
|
|
34
63
|
include: nil,
|
|
35
|
-
exclude: nil
|
|
64
|
+
exclude: nil,
|
|
65
|
+
config: nil
|
|
36
66
|
)
|
|
37
67
|
response = client.connection.post("#{PATH}/#{backend}/#{id}/restore") do |req|
|
|
38
68
|
req.body = {}
|
|
39
69
|
req.body["include"] = include if include
|
|
40
70
|
req.body["exclude"] = exclude if exclude
|
|
71
|
+
req.body["config"] = config if config
|
|
41
72
|
end
|
|
42
73
|
|
|
43
74
|
response.body
|
|
@@ -45,10 +76,34 @@ module Weaviate
|
|
|
45
76
|
|
|
46
77
|
def restore_status(
|
|
47
78
|
backend:,
|
|
48
|
-
id
|
|
79
|
+
id:,
|
|
80
|
+
bucket: nil,
|
|
81
|
+
path: nil
|
|
49
82
|
)
|
|
50
|
-
response = client.connection.get("#{PATH}/#{backend}/#{id}/restore")
|
|
83
|
+
response = client.connection.get("#{PATH}/#{backend}/#{id}/restore") do |req|
|
|
84
|
+
location_params(req, bucket, path)
|
|
85
|
+
end
|
|
51
86
|
response.body
|
|
52
87
|
end
|
|
88
|
+
|
|
89
|
+
# Cancel a restore that is in progress.
|
|
90
|
+
def cancel_restore(
|
|
91
|
+
backend:,
|
|
92
|
+
id:,
|
|
93
|
+
bucket: nil,
|
|
94
|
+
path: nil
|
|
95
|
+
)
|
|
96
|
+
response = client.connection.delete("#{PATH}/#{backend}/#{id}/restore") do |req|
|
|
97
|
+
location_params(req, bucket, path)
|
|
98
|
+
end
|
|
99
|
+
response.success?
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
def location_params(req, bucket, path)
|
|
105
|
+
req.params["bucket"] = bucket unless bucket.nil?
|
|
106
|
+
req.params["path"] = path unless path.nil?
|
|
107
|
+
end
|
|
53
108
|
end
|
|
54
109
|
end
|
data/lib/weaviate/client.rb
CHANGED
|
@@ -1,47 +1,91 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "
|
|
4
|
-
require "graphlient"
|
|
3
|
+
require "logger"
|
|
5
4
|
|
|
6
5
|
module Weaviate
|
|
7
6
|
class Client
|
|
8
|
-
attr_reader :url, :api_key, :model_service, :model_service_api_key, :
|
|
7
|
+
attr_reader :url, :api_key, :model_service, :model_service_api_key, :headers, :proxy, :logger,
|
|
8
|
+
:open_timeout, :read_timeout, :write_timeout
|
|
9
9
|
|
|
10
10
|
API_VERSION = "v1"
|
|
11
11
|
|
|
12
|
+
# Headers Weaviate reads model provider API keys from. Providers that need more than one
|
|
13
|
+
# header (e.g. AWS) or a custom base URL can be configured with the `headers:` option.
|
|
12
14
|
API_KEY_HEADERS = {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
anthropic: "X-Anthropic-Api-Key",
|
|
16
|
+
anyscale: "X-Anyscale-Api-Key",
|
|
15
17
|
azure_openai: "X-Azure-Api-Key",
|
|
16
18
|
cohere: "X-Cohere-Api-Key",
|
|
19
|
+
contextualai: "X-ContextualAI-Api-Key",
|
|
20
|
+
databricks: "X-Databricks-Token",
|
|
21
|
+
deepseek: "X-Deepseek-Api-Key",
|
|
22
|
+
digitalocean: "X-Digitalocean-Api-Key",
|
|
23
|
+
friendliai: "X-Friendli-Token",
|
|
24
|
+
google: "X-Goog-Api-Key",
|
|
25
|
+
google_palm: "X-Palm-Api-Key",
|
|
26
|
+
google_studio: "X-Goog-Studio-Api-Key",
|
|
27
|
+
google_vertex: "X-Goog-Vertex-Api-Key",
|
|
17
28
|
huggingface: "X-HuggingFace-Api-Key",
|
|
18
|
-
|
|
29
|
+
jinaai: "X-JinaAI-Api-Key",
|
|
30
|
+
mistral: "X-Mistral-Api-Key",
|
|
31
|
+
nvidia: "X-Nvidia-Api-Key",
|
|
32
|
+
ollama: "X-Ollama-Not-Used",
|
|
33
|
+
openai: "X-OpenAI-Api-Key",
|
|
34
|
+
twelvelabs: "X-Twelvelabs-Api-Key",
|
|
35
|
+
voyageai: "X-VoyageAI-Api-Key",
|
|
36
|
+
xai: "X-Xai-Api-Key"
|
|
19
37
|
}
|
|
20
38
|
|
|
39
|
+
# @param url [String] The Weaviate instance URL
|
|
40
|
+
# @param api_key [String] The Weaviate API key
|
|
41
|
+
# @param model_service [Symbol] The service used to generate vectors, one of the API_KEY_HEADERS keys
|
|
42
|
+
# @param model_service_api_key [String] The API key for the model service
|
|
43
|
+
# @param headers [Hash] Additional headers sent with every request, e.g. { "X-Aws-Access-Key" => "...", "X-Aws-Secret-Key" => "..." }
|
|
44
|
+
# @param proxy [String] Proxy URL, e.g. "http://user:pass@localhost:8888". Defaults to the http_proxy/https_proxy env vars.
|
|
45
|
+
# @param logger [Logger] Logger for requests and responses
|
|
46
|
+
# @param open_timeout [Numeric] Seconds to wait for a connection to open. Defaults to Net::HTTP's 60 seconds.
|
|
47
|
+
# @param read_timeout [Numeric] Seconds to wait for a response. Defaults to Net::HTTP's 60 seconds.
|
|
48
|
+
# @param write_timeout [Numeric] Seconds to wait for a request to be written. Defaults to Net::HTTP's 60 seconds.
|
|
49
|
+
# @param adapter [Object] Deprecated and ignored, Faraday is no longer used
|
|
21
50
|
def initialize(
|
|
22
51
|
url:,
|
|
23
52
|
api_key: nil,
|
|
24
53
|
model_service: nil,
|
|
25
54
|
model_service_api_key: nil,
|
|
26
|
-
|
|
27
|
-
|
|
55
|
+
headers: {},
|
|
56
|
+
proxy: nil,
|
|
57
|
+
logger: nil,
|
|
58
|
+
open_timeout: nil,
|
|
59
|
+
read_timeout: nil,
|
|
60
|
+
write_timeout: nil,
|
|
61
|
+
adapter: nil
|
|
28
62
|
)
|
|
29
63
|
validate_model_service!(model_service) unless model_service.nil?
|
|
64
|
+
warn "[weaviate-ruby] The `adapter:` option is deprecated and ignored, Faraday is no longer used." unless adapter.nil?
|
|
30
65
|
|
|
31
66
|
@url = url
|
|
32
67
|
@api_key = api_key
|
|
33
68
|
@model_service = model_service
|
|
34
69
|
@model_service_api_key = model_service_api_key
|
|
35
|
-
@
|
|
70
|
+
@headers = headers
|
|
71
|
+
@proxy = proxy
|
|
36
72
|
@logger = logger || Logger.new($stdout)
|
|
73
|
+
@open_timeout = open_timeout
|
|
74
|
+
@read_timeout = read_timeout
|
|
75
|
+
@write_timeout = write_timeout
|
|
37
76
|
end
|
|
38
77
|
|
|
39
78
|
def oidc
|
|
40
79
|
Weaviate::OIDC.new(client: self).get
|
|
41
80
|
end
|
|
42
81
|
|
|
82
|
+
def collections
|
|
83
|
+
@collections ||= Weaviate::Collections.new(client: self)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @deprecated Weaviate now refers to schema classes as collections. Use {#collections} instead.
|
|
43
87
|
def schema
|
|
44
|
-
|
|
88
|
+
collections
|
|
45
89
|
end
|
|
46
90
|
|
|
47
91
|
def meta
|
|
@@ -49,9 +93,10 @@ module Weaviate
|
|
|
49
93
|
@meta.get
|
|
50
94
|
end
|
|
51
95
|
|
|
52
|
-
|
|
96
|
+
# @see Weaviate::Nodes#list
|
|
97
|
+
def nodes(**options)
|
|
53
98
|
@nodes ||= Weaviate::Nodes.new(client: self)
|
|
54
|
-
@nodes.list
|
|
99
|
+
@nodes.list(**options)
|
|
55
100
|
end
|
|
56
101
|
|
|
57
102
|
def live?
|
|
@@ -80,44 +125,47 @@ module Weaviate
|
|
|
80
125
|
@query ||= Weaviate::Query.new(client: self)
|
|
81
126
|
end
|
|
82
127
|
|
|
83
|
-
def
|
|
84
|
-
|
|
128
|
+
def aliases
|
|
129
|
+
@aliases ||= Weaviate::Aliases.new(client: self)
|
|
130
|
+
end
|
|
85
131
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
132
|
+
def users
|
|
133
|
+
@users ||= Weaviate::Users.new(client: self)
|
|
134
|
+
end
|
|
89
135
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
136
|
+
def roles
|
|
137
|
+
@roles ||= Weaviate::Roles.new(client: self)
|
|
138
|
+
end
|
|
93
139
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
)
|
|
140
|
+
def groups
|
|
141
|
+
@groups ||= Weaviate::Groups.new(client: self)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def graphql
|
|
145
|
+
@graphql ||= Weaviate::GraphQL.new(client: self)
|
|
102
146
|
end
|
|
103
147
|
|
|
104
148
|
def connection
|
|
105
|
-
@connection ||=
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
faraday.headers[API_KEY_HEADERS[model_service]] = model_service_api_key if model_service && model_service_api_key
|
|
116
|
-
end
|
|
149
|
+
@connection ||= Weaviate::Connection.new(
|
|
150
|
+
url: "#{url}/#{API_VERSION}",
|
|
151
|
+
headers: default_headers.merge(headers),
|
|
152
|
+
logger: logger,
|
|
153
|
+
proxy: proxy,
|
|
154
|
+
open_timeout: open_timeout,
|
|
155
|
+
read_timeout: read_timeout,
|
|
156
|
+
write_timeout: write_timeout
|
|
157
|
+
)
|
|
117
158
|
end
|
|
118
159
|
|
|
119
160
|
private
|
|
120
161
|
|
|
162
|
+
def default_headers
|
|
163
|
+
headers = {"User-Agent" => "weaviate-ruby/#{Weaviate::VERSION}"}
|
|
164
|
+
headers["Authorization"] = "Bearer #{api_key}" if api_key
|
|
165
|
+
headers[API_KEY_HEADERS[model_service]] = model_service_api_key if model_service && model_service_api_key
|
|
166
|
+
headers
|
|
167
|
+
end
|
|
168
|
+
|
|
121
169
|
def validate_model_service!(model_service)
|
|
122
170
|
unless API_KEY_HEADERS.key?(model_service)
|
|
123
171
|
raise ArgumentError, "Invalid model service: #{model_service}. Acceptable values are: #{API_KEY_HEADERS.keys.join(", ")}"
|
|
@@ -1,27 +1,25 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
3
5
|
module Weaviate
|
|
4
|
-
|
|
6
|
+
# Manage collections (formerly known as "classes" in the Weaviate schema).
|
|
7
|
+
class Collections < Base
|
|
5
8
|
PATH = "schema"
|
|
6
9
|
|
|
7
|
-
#
|
|
10
|
+
# Lists all collections in the Weaviate schema.
|
|
8
11
|
def list
|
|
9
12
|
response = client.connection.get(PATH)
|
|
10
13
|
response.body
|
|
11
14
|
end
|
|
12
15
|
|
|
13
|
-
# Get a single
|
|
16
|
+
# Get a single collection
|
|
14
17
|
def get(class_name:)
|
|
15
18
|
response = client.connection.get("#{PATH}/#{class_name}")
|
|
16
|
-
|
|
17
|
-
if response.success?
|
|
18
|
-
response.body
|
|
19
|
-
elsif response.status == 404
|
|
20
|
-
response.reason_phrase
|
|
21
|
-
end
|
|
19
|
+
response.body
|
|
22
20
|
end
|
|
23
21
|
|
|
24
|
-
# Create a new
|
|
22
|
+
# Create a new collection.
|
|
25
23
|
def create(
|
|
26
24
|
class_name:,
|
|
27
25
|
description: nil,
|
|
@@ -34,7 +32,8 @@ module Weaviate
|
|
|
34
32
|
vectorizer: nil,
|
|
35
33
|
module_config: nil,
|
|
36
34
|
inverted_index_config: nil,
|
|
37
|
-
replication_config: nil
|
|
35
|
+
replication_config: nil,
|
|
36
|
+
vector_config: nil
|
|
38
37
|
)
|
|
39
38
|
response = client.connection.post(PATH) do |req|
|
|
40
39
|
req.body = {}
|
|
@@ -56,26 +55,21 @@ module Weaviate
|
|
|
56
55
|
|
|
57
56
|
req.body["invertedIndexConfig"] = inverted_index_config unless inverted_index_config.nil?
|
|
58
57
|
req.body["replicationConfig"] = replication_config unless replication_config.nil?
|
|
58
|
+
req.body["vectorConfig"] = vector_config unless vector_config.nil?
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
response.body
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
-
# Remove a
|
|
64
|
+
# Remove a collection (and all data in the instances).
|
|
65
65
|
def delete(class_name:)
|
|
66
66
|
response = client.connection.delete("#{PATH}/#{class_name}")
|
|
67
|
-
|
|
68
|
-
if response.success?
|
|
69
|
-
response.body.empty?
|
|
70
|
-
else
|
|
71
|
-
response.body
|
|
72
|
-
end
|
|
67
|
+
response.body.empty?
|
|
73
68
|
end
|
|
74
69
|
|
|
75
|
-
# Update settings of an existing
|
|
76
|
-
#
|
|
77
|
-
#
|
|
78
|
-
# => {"error"=>[{"message"=>"properties cannot be updated through updating the class. Use the add property feature (e.g. \"POST /v1/schema/{className}/properties\") to add additional properties"}]}
|
|
70
|
+
# Update settings of an existing collection.
|
|
71
|
+
# Weaviate replaces the whole collection definition on update, so the provided settings are merged
|
|
72
|
+
# into the current definition. Immutable settings and existing properties cannot be changed.
|
|
79
73
|
def update(
|
|
80
74
|
class_name:,
|
|
81
75
|
description: nil,
|
|
@@ -85,45 +79,73 @@ module Weaviate
|
|
|
85
79
|
module_config: nil,
|
|
86
80
|
properties: nil,
|
|
87
81
|
inverted_index_config: nil,
|
|
88
|
-
replication_config: nil
|
|
82
|
+
replication_config: nil,
|
|
83
|
+
vector_config: nil
|
|
89
84
|
)
|
|
85
|
+
changes = {
|
|
86
|
+
"description" => description,
|
|
87
|
+
"vectorIndexType" => vector_index_type,
|
|
88
|
+
"vectorIndexConfig" => vector_index_config,
|
|
89
|
+
"vectorizer" => vectorizer,
|
|
90
|
+
"moduleConfig" => module_config,
|
|
91
|
+
"properties" => properties,
|
|
92
|
+
"invertedIndexConfig" => inverted_index_config,
|
|
93
|
+
"replicationConfig" => replication_config,
|
|
94
|
+
"vectorConfig" => vector_config
|
|
95
|
+
}.compact
|
|
96
|
+
|
|
90
97
|
response = client.connection.put("#{PATH}/#{class_name}") do |req|
|
|
91
|
-
req.body =
|
|
92
|
-
req.body["class"] = class_name unless class_name.nil?
|
|
93
|
-
req.body["description"] = description unless description.nil?
|
|
94
|
-
req.body["vectorIndexType"] = vector_index_type unless vector_index_type.nil?
|
|
95
|
-
req.body["vectorIndexConfig"] = vector_index_config unless vector_index_config.nil?
|
|
96
|
-
req.body["vectorizer"] = vectorizer unless vectorizer.nil?
|
|
97
|
-
req.body["moduleConfig"] = module_config unless module_config.nil?
|
|
98
|
-
req.body["properties"] = properties unless properties.nil?
|
|
99
|
-
req.body["invertedIndexConfig"] = inverted_index_config unless inverted_index_config.nil?
|
|
100
|
-
req.body["replicationConfig"] = replication_config unless replication_config.nil?
|
|
98
|
+
req.body = deep_merge(get(class_name: class_name), JSON.parse(changes.to_json))
|
|
101
99
|
end
|
|
102
100
|
|
|
103
|
-
if response.success?
|
|
104
|
-
end
|
|
105
101
|
response.body
|
|
106
102
|
end
|
|
107
103
|
|
|
108
|
-
# Adds one or more tenants to a
|
|
104
|
+
# Adds one or more tenants to a collection.
|
|
105
|
+
# @param tenants [Array<String, Hash>] Tenant names, or hashes such as { name: "a", activityStatus: "COLD" }
|
|
109
106
|
def add_tenants(
|
|
110
107
|
class_name:,
|
|
111
108
|
tenants:
|
|
112
109
|
)
|
|
113
110
|
response = client.connection.post("#{PATH}/#{class_name}/tenants") do |req|
|
|
114
|
-
|
|
115
|
-
|
|
111
|
+
req.body = tenants_body(tenants)
|
|
112
|
+
end
|
|
113
|
+
response.body
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Update the activity status of one or more tenants.
|
|
117
|
+
# @param tenants [Array<Hash>] e.g. [{ name: "a", activityStatus: "COLD" }]
|
|
118
|
+
def update_tenants(
|
|
119
|
+
class_name:,
|
|
120
|
+
tenants:
|
|
121
|
+
)
|
|
122
|
+
response = client.connection.put("#{PATH}/#{class_name}/tenants") do |req|
|
|
123
|
+
req.body = tenants_body(tenants)
|
|
116
124
|
end
|
|
117
125
|
response.body
|
|
118
126
|
end
|
|
119
127
|
|
|
120
|
-
#
|
|
128
|
+
# Get a single tenant of a collection.
|
|
129
|
+
def get_tenant(class_name:, tenant:)
|
|
130
|
+
response = client.connection.get("#{PATH}/#{class_name}/tenants/#{tenant}")
|
|
131
|
+
response.body
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Check if a tenant exists.
|
|
135
|
+
def tenant_exists?(class_name:, tenant:)
|
|
136
|
+
response = client.connection.head("#{PATH}/#{class_name}/tenants/#{tenant}")
|
|
137
|
+
response.success?
|
|
138
|
+
rescue Weaviate::ResourceNotFound
|
|
139
|
+
false
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# List tenants of a collection.
|
|
121
143
|
def list_tenants(class_name:)
|
|
122
144
|
response = client.connection.get("#{PATH}/#{class_name}/tenants")
|
|
123
145
|
response.body
|
|
124
146
|
end
|
|
125
147
|
|
|
126
|
-
# Remove one or more tenants from a
|
|
148
|
+
# Remove one or more tenants from a collection.
|
|
127
149
|
def remove_tenants(
|
|
128
150
|
class_name:,
|
|
129
151
|
tenants:
|
|
@@ -132,13 +154,10 @@ module Weaviate
|
|
|
132
154
|
req.body = tenants
|
|
133
155
|
end
|
|
134
156
|
|
|
135
|
-
if response.success?
|
|
136
|
-
end
|
|
137
|
-
|
|
138
157
|
response.body
|
|
139
158
|
end
|
|
140
159
|
|
|
141
|
-
# Add a property to an existing
|
|
160
|
+
# Add a property to an existing collection.
|
|
142
161
|
def add_property(
|
|
143
162
|
class_name:,
|
|
144
163
|
property:
|
|
@@ -147,15 +166,15 @@ module Weaviate
|
|
|
147
166
|
req.body = property
|
|
148
167
|
end
|
|
149
168
|
|
|
150
|
-
if response.success?
|
|
151
|
-
end
|
|
152
169
|
response.body
|
|
153
170
|
end
|
|
154
171
|
|
|
155
|
-
# Inspect the shards of a
|
|
156
|
-
def shards(class_name:)
|
|
157
|
-
response = client.connection.get("#{PATH}/#{class_name}/shards")
|
|
158
|
-
|
|
172
|
+
# Inspect the shards of a collection
|
|
173
|
+
def shards(class_name:, tenant: nil)
|
|
174
|
+
response = client.connection.get("#{PATH}/#{class_name}/shards") do |req|
|
|
175
|
+
req.params["tenant"] = tenant unless tenant.nil?
|
|
176
|
+
end
|
|
177
|
+
response.body
|
|
159
178
|
end
|
|
160
179
|
|
|
161
180
|
# Update shard status
|
|
@@ -166,15 +185,29 @@ module Weaviate
|
|
|
166
185
|
req.body = {}
|
|
167
186
|
req.body["status"] = status
|
|
168
187
|
end
|
|
169
|
-
|
|
188
|
+
|
|
189
|
+
response.body
|
|
170
190
|
end
|
|
171
191
|
|
|
172
192
|
private
|
|
173
193
|
|
|
194
|
+
def tenants_body(tenants)
|
|
195
|
+
Array(tenants).map { |tenant| tenant.is_a?(Hash) ? tenant : {name: tenant.to_s} }
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def deep_merge(current, changes)
|
|
199
|
+
current.merge(changes) do |_key, current_value, new_value|
|
|
200
|
+
(current_value.is_a?(Hash) && new_value.is_a?(Hash)) ? deep_merge(current_value, new_value) : new_value
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
174
204
|
def validate_status!(status)
|
|
175
205
|
unless %w[READONLY READY].include?(status.to_s.upcase)
|
|
176
206
|
raise ArgumentError, 'status must be either "READONLY" or "READY"'
|
|
177
207
|
end
|
|
178
208
|
end
|
|
179
209
|
end
|
|
210
|
+
|
|
211
|
+
# @deprecated Weaviate now refers to schema classes as collections. Use {Collections} instead.
|
|
212
|
+
Schema = Collections
|
|
180
213
|
end
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "net/http"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
module Weaviate
|
|
8
|
+
# A minimal HTTP client built on Net::HTTP
|
|
9
|
+
class Connection
|
|
10
|
+
Request = Struct.new(:params, :headers, :body)
|
|
11
|
+
|
|
12
|
+
Response = Struct.new(:status, :reason_phrase, :headers, :body, keyword_init: true) do
|
|
13
|
+
def success?
|
|
14
|
+
(200..299).cover?(status)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
METHODS = {
|
|
19
|
+
get: Net::HTTP::Get,
|
|
20
|
+
head: Net::HTTP::Head,
|
|
21
|
+
post: Net::HTTP::Post,
|
|
22
|
+
put: Net::HTTP::Put,
|
|
23
|
+
patch: Net::HTTP::Patch,
|
|
24
|
+
delete: Net::HTTP::Delete
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
ERRORS = {
|
|
28
|
+
400 => BadRequestError,
|
|
29
|
+
401 => UnauthorizedError,
|
|
30
|
+
403 => ForbiddenError,
|
|
31
|
+
404 => ResourceNotFound,
|
|
32
|
+
409 => ConflictError,
|
|
33
|
+
422 => UnprocessableEntityError
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
FILTERED_HEADERS = /authorization|api-key|token|secret|access-key/i
|
|
37
|
+
|
|
38
|
+
attr_reader :url, :headers, :logger, :proxy, :timeouts
|
|
39
|
+
|
|
40
|
+
def initialize(url:, headers: {}, logger: nil, proxy: nil, open_timeout: nil, read_timeout: nil, write_timeout: nil)
|
|
41
|
+
@url = url.chomp("/")
|
|
42
|
+
@headers = headers
|
|
43
|
+
@logger = logger
|
|
44
|
+
@proxy = proxy && URI(proxy)
|
|
45
|
+
@timeouts = {open_timeout: open_timeout, read_timeout: read_timeout, write_timeout: write_timeout}.compact
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
METHODS.each_key do |method|
|
|
49
|
+
define_method(method) do |path, &block|
|
|
50
|
+
request(method, path, &block)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def request(method, path)
|
|
57
|
+
req = Request.new({}, {}, nil)
|
|
58
|
+
yield req if block_given?
|
|
59
|
+
|
|
60
|
+
uri = URI("#{url}/#{path}")
|
|
61
|
+
uri.query = URI.encode_www_form(req.params) unless req.params.nil? || req.params.empty?
|
|
62
|
+
|
|
63
|
+
http_request = METHODS.fetch(method).new(uri)
|
|
64
|
+
headers.merge(req.headers).each { |k, v| http_request[k] = v }
|
|
65
|
+
|
|
66
|
+
unless req.body.nil?
|
|
67
|
+
http_request["Content-Type"] = "application/json"
|
|
68
|
+
http_request.body = req.body.is_a?(String) ? req.body : req.body.to_json
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
log_request(http_request, uri)
|
|
72
|
+
|
|
73
|
+
http_response = perform(uri, http_request)
|
|
74
|
+
response = build_response(http_response)
|
|
75
|
+
|
|
76
|
+
log_response(response, http_response)
|
|
77
|
+
|
|
78
|
+
raise_error!(response) unless response.success?
|
|
79
|
+
|
|
80
|
+
response
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def perform(uri, http_request)
|
|
84
|
+
Net::HTTP.start(
|
|
85
|
+
uri.host,
|
|
86
|
+
uri.port,
|
|
87
|
+
proxy ? proxy.host : :ENV,
|
|
88
|
+
proxy&.port,
|
|
89
|
+
proxy&.user,
|
|
90
|
+
proxy&.password,
|
|
91
|
+
use_ssl: uri.scheme == "https",
|
|
92
|
+
**timeouts
|
|
93
|
+
) do |http|
|
|
94
|
+
http.request(http_request)
|
|
95
|
+
end
|
|
96
|
+
rescue Net::OpenTimeout, Net::ReadTimeout, Net::WriteTimeout => e
|
|
97
|
+
raise Weaviate::TimeoutError, e.message
|
|
98
|
+
rescue SocketError, SystemCallError, IOError, OpenSSL::SSL::SSLError => e
|
|
99
|
+
raise Weaviate::ConnectionFailed, e.message
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def build_response(http_response)
|
|
103
|
+
body = http_response.body.to_s
|
|
104
|
+
|
|
105
|
+
if http_response["Content-Type"].to_s.match?(/\bjson\b/) && !body.empty?
|
|
106
|
+
body = JSON.parse(body)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
Response.new(
|
|
110
|
+
status: http_response.code.to_i,
|
|
111
|
+
reason_phrase: http_response.message,
|
|
112
|
+
headers: http_response.to_hash,
|
|
113
|
+
body: body
|
|
114
|
+
)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def raise_error!(response)
|
|
118
|
+
error_class = ERRORS.fetch(response.status) do
|
|
119
|
+
(response.status >= 500) ? ServerError : ClientError
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
raise error_class.new(response: response)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def log_request(http_request, uri)
|
|
126
|
+
return if logger.nil?
|
|
127
|
+
|
|
128
|
+
logger.info("request") { "#{http_request.method} #{uri}" }
|
|
129
|
+
logger.info("request") { format_headers(http_request.each_header.to_h) }
|
|
130
|
+
logger.info("request") { http_request.body } unless http_request.body.nil?
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def log_response(response, http_response)
|
|
134
|
+
return if logger.nil?
|
|
135
|
+
|
|
136
|
+
logger.info("response") { "Status #{response.status}" }
|
|
137
|
+
logger.info("response") { format_headers(http_response.each_header.to_h) }
|
|
138
|
+
logger.info("response") { http_response.body } unless http_response.body.to_s.empty?
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def format_headers(headers)
|
|
142
|
+
headers.map do |k, v|
|
|
143
|
+
"#{k}: #{k.match?(FILTERED_HEADERS) ? "[FILTERED]" : v.inspect}"
|
|
144
|
+
end.join("\n")
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|