typesense 0.8.0pre → 0.8.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: 4170ba5a72493eb15ae0659ded317a9d0b97ee402b0a21f12544f441e269c2df
4
- data.tar.gz: a711dbf8e069670ff4d31a339f10bec22051c8dcaa355bbf8bee8853330eee08
3
+ metadata.gz: 5acfb1b3cbcfd880a7d8fa39a759ad2cb82396e159cdac02d64e72a4047ca264
4
+ data.tar.gz: 1e6d7ca55d559a86842e79e4ffb4db18327206e78c45ff039ff2a8fa59822a7b
5
5
  SHA512:
6
- metadata.gz: 2fed977aae1688b4f1ffa46e86d3007c39dd5dec0d4c485b0887c4076c440d6b35e00c03acc099fca057665372c501a3ecdf686aab93321779ca5bc650f49e12
7
- data.tar.gz: '03198e7930adfcbd8f5e049cd0b7cf5edbf9aa8567d8b3f6d978af66cb74de02bec0dd32715266d712f6571f631e68756cf84f7838a97b6ec17907b3515b191a'
6
+ metadata.gz: 9fa4e3d1155043c18f873ee936eb5bf223d7faac05f99d311afaab9dd49faf2ba9088cf274c6c7402742422d951900f52626480f139949b6ad25e2245ba2f287
7
+ data.tar.gz: fe7fe260705cab7bf02dd0ebf28395860ce27a047e7ff8687c4fed5cbc4baf8aa4f79be4fb2ba08897b0f50eaa6861a9d9b9f4a7647ad959a8387c563b1c1c69
data/README.md CHANGED
@@ -33,7 +33,7 @@ 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.15.1 | \>= v0.8.0 |
36
+ | \>= v0.16.0 | \>= v0.8.0 |
37
37
  | \>= v0.15.0 | \>= v0.7.0 |
38
38
  | \>= v0.12.1 | \>= v0.5.0 |
39
39
  | \>= 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,11 +231,33 @@ 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)
212
235
 
213
- ## If you already have documents in JSONL format, you can also use #import instead, to avoid the JSON parsing overhead:
236
+ ## If you already have documents in JSONL format, you can also pass it directly to #import, to avoid the JSON parsing overhead:
214
237
  # @typesense.collections['companies'].documents.import(documents_in_jsonl_format)
215
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
+
216
261
  ##
217
262
  # Export all documents in a collection in JSON Lines format
218
263
  # We use JSON Lines format for performance reasons. You can choose to parse selected lines as needed, by splitting on \n.
@@ -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,45 +26,40 @@ module Typesense
24
26
  @current_node_index = -1
25
27
  end
26
28
 
27
- def post(endpoint, parameters = {})
28
- headers, query, body = split_post_put_parameters(parameters)
29
-
29
+ def post(endpoint, body_parameters = {}, query_parameters = {})
30
30
  perform_request :post,
31
31
  endpoint,
32
- headers,
33
- params: query,
34
- body: body
32
+ query_parameters: query_parameters,
33
+ body_parameters: body_parameters
35
34
  end
36
35
 
37
- def put(endpoint, parameters = {})
38
- headers, query, body = split_post_put_parameters(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
39
42
 
43
+ def put(endpoint, body_parameters = {}, query_parameters = {})
40
44
  perform_request :put,
41
45
  endpoint,
42
- headers,
43
- params: query,
44
- body: body
46
+ query_parameters: query_parameters,
47
+ body_parameters: body_parameters
45
48
  end
46
49
 
47
- def get(endpoint, parameters = {})
48
- headers, query = extract_headers_and_query_from(parameters)
49
-
50
+ def get(endpoint, query_parameters = {})
50
51
  perform_request :get,
51
52
  endpoint,
52
- headers,
53
- params: query
53
+ query_parameters: query_parameters
54
54
  end
55
55
 
56
- def delete(endpoint, parameters = {})
57
- headers, query = extract_headers_and_query_from(parameters)
58
-
56
+ def delete(endpoint, query_parameters = {})
59
57
  perform_request :delete,
60
58
  endpoint,
61
- headers,
62
- params: query
59
+ query_parameters: query_parameters
63
60
  end
64
61
 
65
- def perform_request(method, endpoint, headers = {}, options = {})
62
+ def perform_request(method, endpoint, query_parameters: nil, body_parameters: nil, additional_headers: {})
66
63
  @configuration.validate!
67
64
  last_exception = nil
68
65
  @logger.debug "Performing #{method.to_s.upcase} request: #{endpoint}"
@@ -72,15 +69,23 @@ module Typesense
72
69
  @logger.debug "Attempting #{method.to_s.upcase} request Try ##{num_tries} to Node #{node[:index]}"
73
70
 
74
71
  begin
75
- response = Typhoeus::Request.new(uri_for(endpoint, node),
76
- {
77
- method: method,
78
- headers: default_headers.merge(headers),
79
- timeout: @connection_timeout_seconds
80
- }.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
81
86
  set_node_healthcheck(node, is_healthy: true) if response.code >= 1 && response.code <= 499
82
87
 
83
- @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}."
84
89
 
85
90
  parsed_response = if response.headers && (response.headers['content-type'] || '').include?('application/json')
86
91
  Oj.load(response.body)
@@ -101,7 +106,7 @@ module Typesense
101
106
  # other languages that might not support the same construct.
102
107
  set_node_healthcheck(node, is_healthy: false)
103
108
  last_exception = e
104
- @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}\""
105
110
  @logger.warn "Sleeping for #{@retry_interval_seconds}s and then retrying request..."
106
111
  sleep @retry_interval_seconds
107
112
  end
@@ -112,43 +117,6 @@ module Typesense
112
117
 
113
118
  private
114
119
 
115
- def split_post_put_parameters(parameters)
116
- if json_request?(parameters)
117
- headers = { 'Content-Type' => 'application/json' }
118
- query = parameters[:query]
119
- body = Oj.dump(sanitize_parameters(parameters))
120
- else
121
- headers = {}
122
- query = parameters[:query]
123
- body = parameters[:body]
124
- end
125
- [headers, query, body]
126
- end
127
-
128
- def extract_headers_and_query_from(parameters)
129
- if json_request?(parameters)
130
- headers = { 'Content-Type' => 'application/json' }
131
- query = sanitize_parameters(parameters)
132
- else
133
- headers = {}
134
- query = parameters[:query]
135
- end
136
- [headers, query]
137
- end
138
-
139
- def json_request?(parameters)
140
- parameters[:as_json].nil? ? true : parameters[:as_json]
141
- end
142
-
143
- def sanitize_parameters(parameters)
144
- sanitized_parameters = parameters.dup
145
- sanitized_parameters.delete(:as_json)
146
- sanitized_parameters.delete(:body)
147
- sanitized_parameters.delete(:query)
148
-
149
- sanitized_parameters
150
- end
151
-
152
120
  def uri_for(endpoint, node)
153
121
  "#{node[:protocol]}://#{node[:host]}:#{node[:port]}#{endpoint}"
154
122
  end
@@ -231,6 +199,7 @@ module Typesense
231
199
 
232
200
  def default_headers
233
201
  {
202
+ 'Content-Type' => 'application/json',
234
203
  API_KEY_HEADER_NAME.to_s => @api_key,
235
204
  'User-Agent' => 'Typesense Ruby Client'
236
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,17 +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, query_parameters = {})
26
- @api_call.post(endpoint_path('import'),
27
- as_json: false,
28
- query: query_parameters,
29
- 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
30
53
  end
31
54
 
32
55
  def export
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Typesense
4
- VERSION = '0.8.0pre'
4
+ VERSION = '0.8.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.8.0pre
4
+ version: 0.8.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-10-13 00:00:00.000000000 Z
11
+ date: 2020-10-25 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: