typesense 0.7.0.pre0 → 0.9.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/README.md +3 -1
- data/examples/client_initialization.rb +16 -16
- data/examples/collections_and_documents.rb +56 -8
- data/lib/typesense/api_call.rb +37 -65
- data/lib/typesense/document.rb +4 -0
- data/lib/typesense/documents.rb +36 -6
- data/lib/typesense/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 717b58a0ddf1e2e262d8cf0e4c0a5e3e2f90a8e0cb259337a5d243a3fb0f901c
|
4
|
+
data.tar.gz: 89a094ce8f884ce68d068122b91a6a2df733cc7e77b128e49dd3396cb4c2f5ce
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 69bbef4fea77ec0a97aa15d34a12168d5853c7eccb4a86866605fc536f8348fb80a31e807ff3b25ae342f95ff3569b421c54fd383164646a1585174bf66ad402
|
7
|
+
data.tar.gz: 8abf2ce30d9858d76150c8f81534d7f3e9bc6ad06a4fc33c2c01e0e6ffcfe49e7b0d02282e862bc95c5574a538966cc51c2f39e43e843abbb68333ef43f368da
|
data/README.md
CHANGED
@@ -25,7 +25,7 @@ Or install it yourself as:
|
|
25
25
|
|
26
26
|
You'll find detailed documentation here: [https://typesense.org/api/](https://typesense.org/api/)
|
27
27
|
|
28
|
-
Here are some examples that
|
28
|
+
Here are some examples with inline comments that walk you through how to use the Ruby client: [examples](examples)
|
29
29
|
|
30
30
|
Tests are also a good place to know how the the library works internally: [spec](spec)
|
31
31
|
|
@@ -33,6 +33,8 @@ Tests are also a good place to know how the the library works internally: [spec]
|
|
33
33
|
|
34
34
|
| Typesense Server | typesense-ruby |
|
35
35
|
|------------------|----------------|
|
36
|
+
| \>= v0.17.0 | \>= v0.9.0 |
|
37
|
+
| \>= v0.16.0 | \>= v0.8.0 |
|
36
38
|
| \>= v0.15.0 | \>= v0.7.0 |
|
37
39
|
| \>= v0.12.1 | \>= v0.5.0 |
|
38
40
|
| \>= v0.12.0 | \>= v0.4.0 |
|
@@ -38,26 +38,26 @@ AwesomePrint.defaults = {
|
|
38
38
|
host: 'localhost',
|
39
39
|
port: 8108,
|
40
40
|
protocol: 'http'
|
41
|
-
},
|
42
|
-
# Uncomment if starting a 3-node cluster, using Option 2 under Setup instructions above
|
43
|
-
{
|
44
|
-
host: 'localhost',
|
45
|
-
port: 7108,
|
46
|
-
protocol: 'http'
|
47
|
-
},
|
48
|
-
{
|
49
|
-
host: 'localhost',
|
50
|
-
port: 9108,
|
51
|
-
protocol: 'http'
|
52
41
|
}
|
42
|
+
# Uncomment if starting a 3-node cluster, using Option 2 under Setup instructions above
|
43
|
+
# {
|
44
|
+
# host: 'localhost',
|
45
|
+
# port: 7108,
|
46
|
+
# protocol: 'http'
|
47
|
+
# },
|
48
|
+
# {
|
49
|
+
# host: 'localhost',
|
50
|
+
# port: 9108,
|
51
|
+
# protocol: 'http'
|
52
|
+
# }
|
53
53
|
],
|
54
54
|
# If this optional key is specified, requests are always sent to this node first if it is healthy
|
55
55
|
# before falling back on the nodes mentioned in the `nodes` key. This is useful when running a distributed set of search clusters.
|
56
|
-
'nearest_node': {
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
},
|
56
|
+
# 'nearest_node': {
|
57
|
+
# 'host': 'localhost',
|
58
|
+
# 'port': '8108',
|
59
|
+
# 'protocol': 'http'
|
60
|
+
# },
|
61
61
|
api_key: 'xyz',
|
62
62
|
num_retries: 10,
|
63
63
|
healthcheck_interval_seconds: 1,
|
@@ -167,6 +167,10 @@ ap document
|
|
167
167
|
# "num_employees" => 5215
|
168
168
|
# }
|
169
169
|
|
170
|
+
# You can also upsert a document, which will update the document if it already exists or create a new one if it doesn't exist
|
171
|
+
document = @typesense.collections['companies'].documents.upsert(document)
|
172
|
+
ap document
|
173
|
+
|
170
174
|
##
|
171
175
|
# Retrieve a document
|
172
176
|
sleep 0.5 # Give Typesense cluster a few hundred ms to create the document on all nodes, before reading it right after (eventually consistent)
|
@@ -180,6 +184,25 @@ ap document
|
|
180
184
|
# "num_employees" => 5215
|
181
185
|
# }
|
182
186
|
|
187
|
+
##
|
188
|
+
# Update a document. Unlike upsert, update will error out if the doc doesn't already exist.
|
189
|
+
document = @typesense.collections['companies'].documents['124'].update(
|
190
|
+
'id' => 1,
|
191
|
+
'num_employees' => 5500
|
192
|
+
)
|
193
|
+
ap document
|
194
|
+
|
195
|
+
# {
|
196
|
+
# "id" => "124",
|
197
|
+
# "num_employees" => 5500
|
198
|
+
# }
|
199
|
+
|
200
|
+
# This should error out, since document 145 doesn't exist
|
201
|
+
# document = @typesense.collections['companies'].documents['145'].update(
|
202
|
+
# 'num_employees' => 5500
|
203
|
+
# )
|
204
|
+
# ap document
|
205
|
+
|
183
206
|
##
|
184
207
|
# Delete a document
|
185
208
|
# Deleting a document, returns the document after deletion
|
@@ -208,19 +231,44 @@ documents = [
|
|
208
231
|
'country' => 'France'
|
209
232
|
}
|
210
233
|
]
|
211
|
-
ap @typesense.collections['companies'].documents.
|
234
|
+
ap @typesense.collections['companies'].documents.import(documents)
|
235
|
+
|
236
|
+
## If you already have documents in JSONL format, you can also pass it directly to #import, to avoid the JSON parsing overhead:
|
237
|
+
# @typesense.collections['companies'].documents.import(documents_in_jsonl_format)
|
238
|
+
|
239
|
+
## You can bulk upsert documents, by adding an upsert action option to #import
|
240
|
+
documents << {
|
241
|
+
'id' => '126',
|
242
|
+
'company_name' => 'Stark Industries 2',
|
243
|
+
'num_employees' => 200,
|
244
|
+
'country' => 'USA'
|
245
|
+
}
|
246
|
+
ap @typesense.collections['companies'].documents.import(documents, action: :upsert)
|
247
|
+
|
248
|
+
## You can bulk update documents, by adding an update action option to #import
|
249
|
+
# `action: update` will throw an error if the document doesn't already exist
|
250
|
+
# This document will error out, since id: 1200 doesn't exist
|
251
|
+
documents << {
|
252
|
+
'id' => '1200',
|
253
|
+
'country' => 'USA'
|
254
|
+
}
|
255
|
+
documents << {
|
256
|
+
'id' => '126',
|
257
|
+
'num_employees' => 300
|
258
|
+
}
|
259
|
+
ap @typesense.collections['companies'].documents.import(documents, action: :update)
|
260
|
+
|
261
|
+
## You can also bulk delete documents, using filter_by fields:
|
262
|
+
ap @typesense.collections['companies'].documents.delete(filter_by: 'num_employees:>100')
|
212
263
|
|
213
264
|
##
|
214
265
|
# Export all documents in a collection in JSON Lines format
|
215
|
-
# We use JSON Lines format for performance reasons. You can choose to parse selected lines
|
266
|
+
# We use JSON Lines format for performance reasons. You can choose to parse selected lines as needed, by splitting on \n.
|
216
267
|
sleep 0.5 # Give Typesense cluster a few hundred ms to create the document on all nodes, before reading it right after (eventually consistent)
|
217
|
-
|
218
|
-
ap
|
268
|
+
jsonl_data = @typesense.collections['companies'].documents.export
|
269
|
+
ap jsonl_data
|
219
270
|
|
220
|
-
#
|
221
|
-
# [0] "{\"company_name\":\"Stark Industries\",\"country\":\"USA\",\"id\":\"124\",\"num_employees\":5215}",
|
222
|
-
# [1] "{\"company_name\":\"Acme Corp\",\"country\":\"France\",\"id\":\"125\",\"num_employees\":1002}"
|
223
|
-
# ]
|
271
|
+
# "{\"company_name\":\"Stark Industries\",\"country\":\"USA\",\"id\":\"124\",\"num_employees\":5215}\n{\"company_name\":\"Acme Corp\",\"country\":\"France\",\"id\":\"125\",\"num_employees\":1002}"
|
224
272
|
|
225
273
|
##
|
226
274
|
# Cleanup
|
data/lib/typesense/api_call.rb
CHANGED
@@ -7,6 +7,8 @@ module Typesense
|
|
7
7
|
class ApiCall
|
8
8
|
API_KEY_HEADER_NAME = 'X-TYPESENSE-API-KEY'
|
9
9
|
|
10
|
+
attr_reader :logger
|
11
|
+
|
10
12
|
def initialize(configuration)
|
11
13
|
@configuration = configuration
|
12
14
|
|
@@ -24,43 +26,40 @@ module Typesense
|
|
24
26
|
@current_node_index = -1
|
25
27
|
end
|
26
28
|
|
27
|
-
def post(endpoint,
|
28
|
-
headers, body = extract_headers_and_body_from(parameters)
|
29
|
-
|
29
|
+
def post(endpoint, body_parameters = {}, query_parameters = {})
|
30
30
|
perform_request :post,
|
31
31
|
endpoint,
|
32
|
-
|
33
|
-
|
32
|
+
query_parameters: query_parameters,
|
33
|
+
body_parameters: body_parameters
|
34
34
|
end
|
35
35
|
|
36
|
-
def
|
37
|
-
|
36
|
+
def patch(endpoint, body_parameters = {}, query_parameters = {})
|
37
|
+
perform_request :patch,
|
38
|
+
endpoint,
|
39
|
+
query_parameters: query_parameters,
|
40
|
+
body_parameters: body_parameters
|
41
|
+
end
|
38
42
|
|
43
|
+
def put(endpoint, body_parameters = {}, query_parameters = {})
|
39
44
|
perform_request :put,
|
40
45
|
endpoint,
|
41
|
-
|
42
|
-
|
46
|
+
query_parameters: query_parameters,
|
47
|
+
body_parameters: body_parameters
|
43
48
|
end
|
44
49
|
|
45
|
-
def get(endpoint,
|
46
|
-
headers, query = extract_headers_and_query_from(parameters)
|
47
|
-
|
50
|
+
def get(endpoint, query_parameters = {})
|
48
51
|
perform_request :get,
|
49
52
|
endpoint,
|
50
|
-
|
51
|
-
params: query
|
53
|
+
query_parameters: query_parameters
|
52
54
|
end
|
53
55
|
|
54
|
-
def delete(endpoint,
|
55
|
-
headers, query = extract_headers_and_query_from(parameters)
|
56
|
-
|
56
|
+
def delete(endpoint, query_parameters = {})
|
57
57
|
perform_request :delete,
|
58
58
|
endpoint,
|
59
|
-
|
60
|
-
params: query
|
59
|
+
query_parameters: query_parameters
|
61
60
|
end
|
62
61
|
|
63
|
-
def perform_request(method, endpoint,
|
62
|
+
def perform_request(method, endpoint, query_parameters: nil, body_parameters: nil, additional_headers: {})
|
64
63
|
@configuration.validate!
|
65
64
|
last_exception = nil
|
66
65
|
@logger.debug "Performing #{method.to_s.upcase} request: #{endpoint}"
|
@@ -70,15 +69,23 @@ module Typesense
|
|
70
69
|
@logger.debug "Attempting #{method.to_s.upcase} request Try ##{num_tries} to Node #{node[:index]}"
|
71
70
|
|
72
71
|
begin
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
72
|
+
request_options = {
|
73
|
+
method: method,
|
74
|
+
timeout: @connection_timeout_seconds,
|
75
|
+
headers: default_headers.merge(additional_headers)
|
76
|
+
}
|
77
|
+
request_options.merge!(params: query_parameters) unless query_parameters.nil?
|
78
|
+
|
79
|
+
unless body_parameters.nil?
|
80
|
+
body = body_parameters
|
81
|
+
body = Oj.dump(body_parameters) if request_options[:headers]['Content-Type'] == 'application/json'
|
82
|
+
request_options.merge!(body: body)
|
83
|
+
end
|
84
|
+
|
85
|
+
response = Typhoeus::Request.new(uri_for(endpoint, node), request_options).run
|
79
86
|
set_node_healthcheck(node, is_healthy: true) if response.code >= 1 && response.code <= 499
|
80
87
|
|
81
|
-
@logger.debug "Request to Node #{node[:index]} was successfully made (at the network layer). Response Code was #{response.code}."
|
88
|
+
@logger.debug "Request #{method}:#{uri_for(endpoint, node)} to Node #{node[:index]} was successfully made (at the network layer). Response Code was #{response.code}."
|
82
89
|
|
83
90
|
parsed_response = if response.headers && (response.headers['content-type'] || '').include?('application/json')
|
84
91
|
Oj.load(response.body)
|
@@ -91,8 +98,7 @@ module Typesense
|
|
91
98
|
|
92
99
|
exception_message = (parsed_response && parsed_response['message']) || 'Error'
|
93
100
|
raise custom_exception_klass_for(response), exception_message
|
94
|
-
rescue
|
95
|
-
Errno::EINVAL, Errno::ENETDOWN, Errno::ENETUNREACH, Errno::ENETRESET, Errno::ECONNABORTED, Errno::ECONNRESET,
|
101
|
+
rescue Errno::EINVAL, Errno::ENETDOWN, Errno::ENETUNREACH, Errno::ENETRESET, Errno::ECONNABORTED, Errno::ECONNRESET,
|
96
102
|
Errno::ETIMEDOUT, Errno::ECONNREFUSED, Errno::EHOSTDOWN, Errno::EHOSTUNREACH,
|
97
103
|
Typesense::Error::TimeoutError, Typesense::Error::ServerError, Typesense::Error::HTTPStatus0Error => e
|
98
104
|
# Rescue network layer exceptions and HTTP 5xx errors, so the loop can continue.
|
@@ -100,7 +106,7 @@ module Typesense
|
|
100
106
|
# other languages that might not support the same construct.
|
101
107
|
set_node_healthcheck(node, is_healthy: false)
|
102
108
|
last_exception = e
|
103
|
-
@logger.warn "Request to Node #{node[:index]} failed due to \"#{e.class}: #{e.message}\""
|
109
|
+
@logger.warn "Request #{method}:#{uri_for(endpoint, node)} to Node #{node[:index]} failed due to \"#{e.class}: #{e.message}\""
|
104
110
|
@logger.warn "Sleeping for #{@retry_interval_seconds}s and then retrying request..."
|
105
111
|
sleep @retry_interval_seconds
|
106
112
|
end
|
@@ -111,41 +117,6 @@ module Typesense
|
|
111
117
|
|
112
118
|
private
|
113
119
|
|
114
|
-
def extract_headers_and_body_from(parameters)
|
115
|
-
if json_request?(parameters)
|
116
|
-
headers = { 'Content-Type' => 'application/json' }
|
117
|
-
body = Oj.dump(sanitize_parameters(parameters))
|
118
|
-
else
|
119
|
-
headers = {}
|
120
|
-
body = parameters[:body]
|
121
|
-
end
|
122
|
-
[headers, body]
|
123
|
-
end
|
124
|
-
|
125
|
-
def extract_headers_and_query_from(parameters)
|
126
|
-
if json_request?(parameters)
|
127
|
-
headers = { 'Content-Type' => 'application/json' }
|
128
|
-
query = sanitize_parameters(parameters)
|
129
|
-
else
|
130
|
-
headers = {}
|
131
|
-
query = parameters[:query]
|
132
|
-
end
|
133
|
-
[headers, query]
|
134
|
-
end
|
135
|
-
|
136
|
-
def json_request?(parameters)
|
137
|
-
parameters[:as_json].nil? ? true : parameters[:as_json]
|
138
|
-
end
|
139
|
-
|
140
|
-
def sanitize_parameters(parameters)
|
141
|
-
sanitized_parameters = parameters.dup
|
142
|
-
sanitized_parameters.delete(:as_json)
|
143
|
-
sanitized_parameters.delete(:body)
|
144
|
-
sanitized_parameters.delete(:query)
|
145
|
-
|
146
|
-
sanitized_parameters
|
147
|
-
end
|
148
|
-
|
149
120
|
def uri_for(endpoint, node)
|
150
121
|
"#{node[:protocol]}://#{node[:host]}:#{node[:port]}#{endpoint}"
|
151
122
|
end
|
@@ -228,6 +199,7 @@ module Typesense
|
|
228
199
|
|
229
200
|
def default_headers
|
230
201
|
{
|
202
|
+
'Content-Type' => 'application/json',
|
231
203
|
API_KEY_HEADER_NAME.to_s => @api_key,
|
232
204
|
'User-Agent' => 'Typesense Ruby Client'
|
233
205
|
}
|
data/lib/typesense/document.rb
CHANGED
data/lib/typesense/documents.rb
CHANGED
@@ -16,14 +16,40 @@ module Typesense
|
|
16
16
|
@api_call.post(endpoint_path, document)
|
17
17
|
end
|
18
18
|
|
19
|
-
def
|
20
|
-
|
21
|
-
results_in_jsonl_format = import(documents_in_jsonl_format)
|
22
|
-
results_in_jsonl_format.split("\n").map { |r| Oj.load(r) }
|
19
|
+
def upsert(document)
|
20
|
+
@api_call.post(endpoint_path, document, action: :upsert)
|
23
21
|
end
|
24
22
|
|
25
|
-
def
|
26
|
-
@api_call.post(endpoint_path
|
23
|
+
def update(document)
|
24
|
+
@api_call.post(endpoint_path, document, action: :update)
|
25
|
+
end
|
26
|
+
|
27
|
+
def create_many(documents, options = {})
|
28
|
+
@api_call.logger.warn('#create_many is deprecated and will be removed in a future version. Use #import instead, which now takes both an array of documents or a JSONL string of documents')
|
29
|
+
import(documents, options)
|
30
|
+
end
|
31
|
+
|
32
|
+
# @param [Array,String] documents An array of document hashes or a JSONL string of documents.
|
33
|
+
def import(documents, options = {})
|
34
|
+
documents_in_jsonl_format = if documents.is_a?(Array)
|
35
|
+
documents.map { |document| Oj.dump(document) }.join("\n")
|
36
|
+
else
|
37
|
+
documents
|
38
|
+
end
|
39
|
+
|
40
|
+
results_in_jsonl_format = @api_call.perform_request(
|
41
|
+
'post',
|
42
|
+
endpoint_path('import'),
|
43
|
+
query_parameters: options,
|
44
|
+
body_parameters: documents_in_jsonl_format,
|
45
|
+
additional_headers: { 'Content-Type' => 'text/plain' }
|
46
|
+
)
|
47
|
+
|
48
|
+
if documents.is_a?(Array)
|
49
|
+
results_in_jsonl_format.split("\n").map { |r| Oj.load(r) }
|
50
|
+
else
|
51
|
+
results_in_jsonl_format
|
52
|
+
end
|
27
53
|
end
|
28
54
|
|
29
55
|
def export
|
@@ -38,6 +64,10 @@ module Typesense
|
|
38
64
|
@documents[document_id] ||= Document.new(@collection_name, document_id, @api_call)
|
39
65
|
end
|
40
66
|
|
67
|
+
def delete(query_parameters = {})
|
68
|
+
@api_call.delete(endpoint_path, query_parameters)
|
69
|
+
end
|
70
|
+
|
41
71
|
private
|
42
72
|
|
43
73
|
def endpoint_path(operation = nil)
|
data/lib/typesense/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: typesense
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Typesense, Inc.
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-11-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: awesome_print
|
@@ -309,9 +309,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
309
309
|
version: '2.4'
|
310
310
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
311
311
|
requirements:
|
312
|
-
- - "
|
312
|
+
- - ">="
|
313
313
|
- !ruby/object:Gem::Version
|
314
|
-
version:
|
314
|
+
version: '0'
|
315
315
|
requirements: []
|
316
316
|
rubygems_version: 3.0.6
|
317
317
|
signing_key:
|