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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 680a4251ea189d959a84665eb824d50efabdbe3359be1f787f23d9ea230ab16f
4
- data.tar.gz: cdbb99c7ab1d32408c7100c3d1167a1ce8d1177014b80051ad4a5e2c13fbec99
3
+ metadata.gz: 717b58a0ddf1e2e262d8cf0e4c0a5e3e2f90a8e0cb259337a5d243a3fb0f901c
4
+ data.tar.gz: 89a094ce8f884ce68d068122b91a6a2df733cc7e77b128e49dd3396cb4c2f5ce
5
5
  SHA512:
6
- metadata.gz: b161ddf9e1d3445934c77444d0a7d4cfb2ae6efb8879dd9f15020d286f4347bcb40933c7ad201b5d9b8cc5a3848b484586984bb07d7ac584a3e325941f08ca50
7
- data.tar.gz: 04506357d9defab42a6c85d3c06b963670050944a26e6e0378cbca06b82ad2254312de2862f1fab72d8106a0dcebdc42c3c92c96bb596da9d67de4cfd1b0295c
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 show you how the Ruby client works: [examples](examples)
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
- 'host': 'localhost',
58
- 'port': '8108',
59
- 'protocol': 'http'
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.create_many(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 (elements in the array) as needed.
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
- array_of_json_strings = @typesense.collections['companies'].documents.export
218
- ap array_of_json_strings
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
@@ -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, parameters = {})
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
- headers,
33
- body: body
32
+ query_parameters: query_parameters,
33
+ body_parameters: body_parameters
34
34
  end
35
35
 
36
- def put(endpoint, parameters = {})
37
- headers, body = extract_headers_and_body_from(parameters)
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
- headers,
42
- body: body
46
+ query_parameters: query_parameters,
47
+ body_parameters: body_parameters
43
48
  end
44
49
 
45
- def get(endpoint, parameters = {})
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
- headers,
51
- params: query
53
+ query_parameters: query_parameters
52
54
  end
53
55
 
54
- def delete(endpoint, parameters = {})
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
- headers,
60
- params: query
59
+ query_parameters: query_parameters
61
60
  end
62
61
 
63
- def perform_request(method, endpoint, headers = {}, options = {})
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
- response = Typhoeus::Request.new(uri_for(endpoint, node),
74
- {
75
- method: method,
76
- headers: default_headers.merge(headers),
77
- timeout: @connection_timeout_seconds
78
- }.merge(options)).run
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 SocketError, EOFError,
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
  }
@@ -16,6 +16,10 @@ module Typesense
16
16
  @api_call.delete(endpoint_path)
17
17
  end
18
18
 
19
+ def update(partial_document)
20
+ @api_call.patch(endpoint_path, partial_document)
21
+ end
22
+
19
23
  private
20
24
 
21
25
  def endpoint_path
@@ -16,14 +16,40 @@ module Typesense
16
16
  @api_call.post(endpoint_path, document)
17
17
  end
18
18
 
19
- def create_many(documents)
20
- documents_in_jsonl_format = documents.map { |document| Oj.dump(document) }.join("\n")
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 import(documents_in_jsonl_format)
26
- @api_call.post(endpoint_path('import'), as_json: false, body: documents_in_jsonl_format)
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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Typesense
4
- VERSION = '0.7.0.pre0'
4
+ VERSION = '0.9.0'
5
5
  end
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.7.0.pre0
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-09-15 00:00:00.000000000 Z
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: 1.3.1
314
+ version: '0'
315
315
  requirements: []
316
316
  rubygems_version: 3.0.6
317
317
  signing_key: