typesense 0.1.1 → 0.5.2
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/.circleci/config.yml +4 -2
- data/.gitignore +2 -0
- data/.rubocop.yml +7 -0
- data/.rubocop_todo.yml +36 -27
- data/LICENSE +198 -10
- data/README.md +10 -2
- data/codecov.yml +10 -0
- data/examples/aliases.rb +58 -0
- data/examples/client_initialization.rb +67 -0
- data/examples/collections_and_documents.rb +45 -68
- data/examples/keys.rb +127 -0
- data/examples/overrides.rb +108 -0
- data/examples/search.rb +26 -53
- data/lib/typesense.rb +8 -0
- data/lib/typesense/alias.rb +24 -0
- data/lib/typesense/aliases.rb +30 -0
- data/lib/typesense/api_call.rb +186 -69
- data/lib/typesense/client.rb +12 -3
- data/lib/typesense/collection.rb +8 -6
- data/lib/typesense/collections.rb +6 -6
- data/lib/typesense/configuration.rb +35 -19
- data/lib/typesense/debug.rb +3 -3
- data/lib/typesense/document.rb +4 -4
- data/lib/typesense/documents.rb +11 -6
- data/lib/typesense/error.rb +6 -0
- data/lib/typesense/health.rb +15 -0
- data/lib/typesense/key.rb +24 -0
- data/lib/typesense/keys.rb +34 -0
- data/lib/typesense/metrics.rb +15 -0
- data/lib/typesense/override.rb +25 -0
- data/lib/typesense/overrides.rb +31 -0
- data/lib/typesense/version.rb +1 -1
- data/typesense.gemspec +14 -11
- metadata +79 -25
data/examples/search.rb
CHANGED
@@ -3,89 +3,62 @@
|
|
3
3
|
##
|
4
4
|
# These examples walk you through operations specifically related to search
|
5
5
|
|
6
|
-
require_relative '
|
7
|
-
require 'awesome_print'
|
8
|
-
|
9
|
-
AwesomePrint.defaults = {
|
10
|
-
indent: -2
|
11
|
-
}
|
12
|
-
|
13
|
-
##
|
14
|
-
# Setup
|
15
|
-
#
|
16
|
-
# Start the master
|
17
|
-
# $ docker run -p 8108:8108 -it -v/tmp/typesense-data-master/:/data -it typesense/typesense:0.8.0-rc1 --data-dir /data --api-key=abcd --listen-port 8108
|
18
|
-
#
|
19
|
-
# Start the read replica
|
20
|
-
# $ docker run -p 8109:8109 -it -v/tmp/typesense-data-read-replica-1/:/data -it typesense/typesense:0.8.0-rc1 --data-dir /data --api-key=wxyz --listen-port 8109 --master http://localhost:8108
|
21
|
-
|
22
|
-
##
|
23
|
-
# Create a client
|
24
|
-
typesense = Typesense::Client.new(
|
25
|
-
master_node: {
|
26
|
-
host: 'localhost',
|
27
|
-
port: 8108,
|
28
|
-
protocol: 'http',
|
29
|
-
api_key: 'abcd'
|
30
|
-
},
|
31
|
-
read_replica_nodes: [
|
32
|
-
{
|
33
|
-
host: 'localhost',
|
34
|
-
port: 8109,
|
35
|
-
protocol: 'http',
|
36
|
-
api_key: 'wxyz'
|
37
|
-
}
|
38
|
-
],
|
39
|
-
timeout_seconds: 10
|
40
|
-
)
|
6
|
+
require_relative './client_initialization'
|
41
7
|
|
42
8
|
##
|
43
9
|
# Create a collection
|
44
10
|
schema = {
|
45
|
-
'name'
|
46
|
-
'fields'
|
11
|
+
'name' => 'companies',
|
12
|
+
'fields' => [
|
47
13
|
{
|
48
14
|
'name' => 'company_name',
|
49
15
|
'type' => 'string'
|
50
16
|
},
|
51
17
|
{
|
52
|
-
'name'
|
53
|
-
'type'
|
18
|
+
'name' => 'num_employees',
|
19
|
+
'type' => 'int32'
|
54
20
|
},
|
55
21
|
{
|
56
|
-
'name'
|
57
|
-
'type'
|
22
|
+
'name' => 'country',
|
23
|
+
'type' => 'string',
|
58
24
|
'facet' => true
|
59
25
|
}
|
60
26
|
],
|
61
27
|
'default_sorting_field' => 'num_employees'
|
62
28
|
}
|
63
29
|
|
64
|
-
|
30
|
+
# Delete the collection if it already exists
|
31
|
+
begin
|
32
|
+
@typesense.collections['companies'].delete
|
33
|
+
rescue Typesense::Error::ObjectNotFound
|
34
|
+
end
|
35
|
+
|
36
|
+
# Now create the collection
|
37
|
+
@typesense.collections.create(schema)
|
65
38
|
|
66
39
|
# Let's create a couple documents for us to use in our search examples
|
67
|
-
typesense.collections['companies'].documents.create(
|
40
|
+
@typesense.collections['companies'].documents.create(
|
68
41
|
'id' => '124',
|
69
42
|
'company_name' => 'Stark Industries',
|
70
43
|
'num_employees' => 5215,
|
71
44
|
'country' => 'USA'
|
72
45
|
)
|
73
46
|
|
74
|
-
typesense.collections['companies'].documents.create(
|
47
|
+
@typesense.collections['companies'].documents.create(
|
75
48
|
'id' => '127',
|
76
49
|
'company_name' => 'Stark Corp',
|
77
50
|
'num_employees' => 1031,
|
78
51
|
'country' => 'USA'
|
79
52
|
)
|
80
53
|
|
81
|
-
typesense.collections['companies'].documents.create(
|
54
|
+
@typesense.collections['companies'].documents.create(
|
82
55
|
'id' => '125',
|
83
56
|
'company_name' => 'Acme Corp',
|
84
57
|
'num_employees' => 1002,
|
85
58
|
'country' => 'France'
|
86
59
|
)
|
87
60
|
|
88
|
-
typesense.collections['companies'].documents.create(
|
61
|
+
@typesense.collections['companies'].documents.create(
|
89
62
|
'id' => '126',
|
90
63
|
'company_name' => 'Doofenshmirtz Inc',
|
91
64
|
'num_employees' => 2,
|
@@ -94,7 +67,7 @@ typesense.collections['companies'].documents.create(
|
|
94
67
|
|
95
68
|
##
|
96
69
|
# Search for documents
|
97
|
-
results = typesense.collections['companies'].documents.search(
|
70
|
+
results = @typesense.collections['companies'].documents.search(
|
98
71
|
'q' => 'Stark',
|
99
72
|
'query_by' => 'company_name'
|
100
73
|
)
|
@@ -133,11 +106,11 @@ ap results
|
|
133
106
|
|
134
107
|
##
|
135
108
|
# Search for more documents
|
136
|
-
results = typesense.collections['companies'].documents.search(
|
109
|
+
results = @typesense.collections['companies'].documents.search(
|
137
110
|
'q' => 'Inc',
|
138
|
-
'query_by'
|
111
|
+
'query_by' => 'company_name',
|
139
112
|
'filter_by' => 'num_employees:<100',
|
140
|
-
'sort_by'
|
113
|
+
'sort_by' => 'num_employees:desc'
|
141
114
|
)
|
142
115
|
ap results
|
143
116
|
|
@@ -163,8 +136,8 @@ ap results
|
|
163
136
|
|
164
137
|
##
|
165
138
|
# Search for more documents
|
166
|
-
results = typesense.collections['companies'].documents.search(
|
167
|
-
'q'
|
139
|
+
results = @typesense.collections['companies'].documents.search(
|
140
|
+
'q' => 'Non-existent',
|
168
141
|
'query_by' => 'company_name'
|
169
142
|
)
|
170
143
|
ap results
|
@@ -179,4 +152,4 @@ ap results
|
|
179
152
|
##
|
180
153
|
# Cleanup
|
181
154
|
# Drop the collection
|
182
|
-
typesense.collections['companies'].delete
|
155
|
+
@typesense.collections['companies'].delete
|
data/lib/typesense.rb
CHANGED
@@ -11,5 +11,13 @@ require_relative 'typesense/collections'
|
|
11
11
|
require_relative 'typesense/collection'
|
12
12
|
require_relative 'typesense/documents'
|
13
13
|
require_relative 'typesense/document'
|
14
|
+
require_relative 'typesense/overrides'
|
15
|
+
require_relative 'typesense/override'
|
16
|
+
require_relative 'typesense/aliases'
|
17
|
+
require_relative 'typesense/alias'
|
18
|
+
require_relative 'typesense/keys'
|
19
|
+
require_relative 'typesense/key'
|
14
20
|
require_relative 'typesense/debug'
|
21
|
+
require_relative 'typesense/health'
|
22
|
+
require_relative 'typesense/metrics'
|
15
23
|
require_relative 'typesense/error'
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Typesense
|
4
|
+
class Alias
|
5
|
+
def initialize(name, api_call)
|
6
|
+
@name = name
|
7
|
+
@api_call = api_call
|
8
|
+
end
|
9
|
+
|
10
|
+
def retrieve
|
11
|
+
@api_call.get(endpoint_path)
|
12
|
+
end
|
13
|
+
|
14
|
+
def delete
|
15
|
+
@api_call.delete(endpoint_path)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def endpoint_path
|
21
|
+
"#{Aliases::RESOURCE_PATH}/#{@name}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Typesense
|
4
|
+
class Aliases
|
5
|
+
RESOURCE_PATH = '/aliases'
|
6
|
+
|
7
|
+
def initialize(api_call)
|
8
|
+
@api_call = api_call
|
9
|
+
@aliases = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def upsert(alias_name, mapping)
|
13
|
+
@api_call.put(endpoint_path(alias_name), mapping)
|
14
|
+
end
|
15
|
+
|
16
|
+
def retrieve
|
17
|
+
@api_call.get(RESOURCE_PATH)
|
18
|
+
end
|
19
|
+
|
20
|
+
def [](alias_name)
|
21
|
+
@aliases[alias_name] ||= Alias.new(alias_name, @api_call)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def endpoint_path(alias_name)
|
27
|
+
"#{Aliases::RESOURCE_PATH}/#{alias_name}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/typesense/api_call.rb
CHANGED
@@ -10,106 +10,223 @@ module Typesense
|
|
10
10
|
|
11
11
|
def initialize(configuration)
|
12
12
|
@configuration = configuration
|
13
|
+
|
14
|
+
@api_key = @configuration.api_key
|
15
|
+
@nodes = @configuration.nodes.dup # Make a copy, since we'll be adding additional metadata to the nodes
|
16
|
+
@nearest_node = @configuration.nearest_node.dup
|
17
|
+
@connection_timeout_seconds = @configuration.connection_timeout_seconds
|
18
|
+
@healthcheck_interval_seconds = @configuration.healthcheck_interval_seconds
|
19
|
+
@num_retries_per_request = @configuration.num_retries
|
20
|
+
@retry_interval_seconds = @configuration.retry_interval_seconds
|
21
|
+
|
22
|
+
@logger = @configuration.logger
|
23
|
+
|
24
|
+
initialize_metadata_for_nodes
|
25
|
+
@current_node_index = -1
|
13
26
|
end
|
14
27
|
|
15
28
|
def post(endpoint, parameters = {})
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end.parsed_response
|
29
|
+
headers, body = extract_headers_and_body_from(parameters)
|
30
|
+
|
31
|
+
perform_request :post,
|
32
|
+
endpoint,
|
33
|
+
body: body,
|
34
|
+
headers: default_headers.merge(headers)
|
23
35
|
end
|
24
36
|
|
25
|
-
def
|
26
|
-
|
37
|
+
def put(endpoint, parameters = {})
|
38
|
+
headers, body = extract_headers_and_body_from(parameters)
|
39
|
+
|
40
|
+
perform_request :put,
|
41
|
+
endpoint,
|
42
|
+
body: body,
|
43
|
+
headers: default_headers.merge(headers)
|
27
44
|
end
|
28
45
|
|
29
|
-
def
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
end
|
46
|
+
def get(endpoint, parameters = {})
|
47
|
+
headers, query = extract_headers_and_query_from(parameters)
|
48
|
+
|
49
|
+
perform_request :get,
|
50
|
+
endpoint,
|
51
|
+
query: query,
|
52
|
+
headers: default_headers.merge(headers)
|
37
53
|
end
|
38
54
|
|
39
55
|
def delete(endpoint, parameters = {})
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
56
|
+
headers, query = extract_headers_and_query_from(parameters)
|
57
|
+
|
58
|
+
perform_request :delete,
|
59
|
+
endpoint,
|
60
|
+
query: query,
|
61
|
+
headers: default_headers.merge(headers)
|
62
|
+
end
|
63
|
+
|
64
|
+
def perform_request(method, endpoint, options = {})
|
65
|
+
@configuration.validate!
|
66
|
+
last_exception = nil
|
67
|
+
@logger.debug "Performing #{method.to_s.upcase} request: #{endpoint}"
|
68
|
+
(1..(@num_retries_per_request + 1)).each do |num_tries|
|
69
|
+
node = next_node
|
70
|
+
|
71
|
+
@logger.debug "Attempting #{method.to_s.upcase} request Try ##{num_tries} to Node #{node[:index]}"
|
72
|
+
|
73
|
+
begin
|
74
|
+
response_object = self.class.send(method,
|
75
|
+
uri_for(endpoint, node),
|
76
|
+
default_options.merge(options))
|
77
|
+
response_code = response_object.response.code.to_i
|
78
|
+
set_node_healthcheck(node, is_healthy: true) if response_code >= 1 && response_code <= 499
|
79
|
+
|
80
|
+
@logger.debug "Request to Node #{node[:index]} was successfully made (at the network layer). Response Code was #{response_code}."
|
81
|
+
|
82
|
+
# If response is 2xx return the object, else raise the response as an exception
|
83
|
+
return response_object.parsed_response if response_object.response.code_type <= Net::HTTPSuccess # 2xx
|
84
|
+
|
85
|
+
raise custom_exception_klass_for(response_object.response), response_object.parsed_response['message'] || 'Error message not available'
|
86
|
+
rescue Net::ReadTimeout, Net::OpenTimeout,
|
87
|
+
EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError,
|
88
|
+
Errno::EINVAL, Errno::ENETDOWN, Errno::ENETUNREACH, Errno::ENETRESET, Errno::ECONNABORTED, Errno::ECONNRESET,
|
89
|
+
Errno::ETIMEDOUT, Errno::ECONNREFUSED, Errno::EHOSTDOWN, Errno::EHOSTUNREACH,
|
90
|
+
Timeout::Error, HTTParty::ResponseError, Typesense::Error::ServerError, Typesense::Error::HTTPStatus0Error => e
|
91
|
+
# Rescue network layer exceptions and HTTP 5xx errors, so the loop can continue.
|
92
|
+
# Using loops for retries instead of rescue...retry to maintain consistency with client libraries in
|
93
|
+
# other languages that might not support the same construct.
|
94
|
+
set_node_healthcheck(node, is_healthy: false)
|
95
|
+
last_exception = e
|
96
|
+
@logger.warn "Request to Node #{node[:index]} failed due to \"#{e.class}: #{e.message}\""
|
97
|
+
@logger.warn "Sleeping for #{@retry_interval_seconds}s and then retrying request..."
|
98
|
+
sleep @retry_interval_seconds
|
99
|
+
end
|
100
|
+
end
|
101
|
+
@logger.debug "No retries left. Raising last error \"#{last_exception.class}: #{last_exception.message}\"..."
|
102
|
+
raise last_exception
|
47
103
|
end
|
48
104
|
|
49
105
|
private
|
50
106
|
|
51
|
-
def
|
52
|
-
if
|
53
|
-
|
107
|
+
def extract_headers_and_body_from(parameters)
|
108
|
+
if json_request?(parameters)
|
109
|
+
headers = { 'Content-Type' => 'application/json' }
|
110
|
+
body = sanitize_parameters(parameters).to_json
|
54
111
|
else
|
55
|
-
|
112
|
+
headers = {}
|
113
|
+
body = parameters[:body]
|
56
114
|
end
|
115
|
+
[headers, body]
|
57
116
|
end
|
58
117
|
|
59
|
-
def
|
60
|
-
|
118
|
+
def extract_headers_and_query_from(parameters)
|
119
|
+
if json_request?(parameters)
|
120
|
+
headers = { 'Content-Type' => 'application/json' }
|
121
|
+
query = sanitize_parameters(parameters)
|
122
|
+
else
|
123
|
+
headers = {}
|
124
|
+
query = parameters[:query]
|
125
|
+
end
|
126
|
+
[headers, query]
|
127
|
+
end
|
128
|
+
|
129
|
+
def json_request?(parameters)
|
130
|
+
parameters[:as_json].nil? ? true : parameters[:as_json]
|
131
|
+
end
|
61
132
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
raise error_klass, response_object.parsed_response['message']
|
87
|
-
rescue Net::ReadTimeout, Net::OpenTimeout,
|
88
|
-
EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError,
|
89
|
-
Errno::EINVAL, Errno::ENETDOWN, Errno::ENETUNREACH, Errno::ENETRESET, Errno::ECONNABORTED, Errno::ECONNRESET,
|
90
|
-
Errno::ETIMEDOUT, Errno::ECONNREFUSED, Errno::EHOSTDOWN, Errno::EHOSTUNREACH,
|
91
|
-
Timeout::Error, Error::ServerError, HTTParty::ResponseError
|
92
|
-
if (use_read_replicas == :use_read_replicas || use_read_replicas == true) &&
|
93
|
-
!@configuration.read_replica_nodes.nil?
|
94
|
-
node = :read_replica
|
95
|
-
node_index += 1
|
96
|
-
|
97
|
-
retry unless @configuration.read_replica_nodes[node_index].nil?
|
133
|
+
def sanitize_parameters(parameters)
|
134
|
+
sanitized_parameters = parameters.dup
|
135
|
+
sanitized_parameters.delete(:as_json)
|
136
|
+
sanitized_parameters.delete(:body)
|
137
|
+
sanitized_parameters.delete(:query)
|
138
|
+
|
139
|
+
sanitized_parameters
|
140
|
+
end
|
141
|
+
|
142
|
+
def uri_for(endpoint, node)
|
143
|
+
"#{node[:protocol]}://#{node[:host]}:#{node[:port]}#{endpoint}"
|
144
|
+
end
|
145
|
+
|
146
|
+
## Attempts to find the next healthy node, looping through the list of nodes once.
|
147
|
+
# But if no healthy nodes are found, it will just return the next node, even if it's unhealthy
|
148
|
+
# so we can try the request for good measure, in case that node has become healthy since
|
149
|
+
def next_node
|
150
|
+
# Check if nearest_node is set and is healthy, if so return it
|
151
|
+
unless @nearest_node.nil?
|
152
|
+
@logger.debug "Nodes health: Node #{@nearest_node[:index]} is #{@nearest_node[:is_healthy] == true ? 'Healthy' : 'Unhealthy'}"
|
153
|
+
if @nearest_node[:is_healthy] == true || node_due_for_healthcheck?(@nearest_node)
|
154
|
+
@logger.debug "Updated current node to Node #{@nearest_node[:index]}"
|
155
|
+
return @nearest_node
|
98
156
|
end
|
157
|
+
@logger.debug 'Falling back to individual nodes'
|
158
|
+
end
|
159
|
+
|
160
|
+
# Fallback to nodes as usual
|
161
|
+
@logger.debug "Nodes health: #{@nodes.each_with_index.map { |node, i| "Node #{i} is #{node[:is_healthy] == true ? 'Healthy' : 'Unhealthy'}" }.join(' || ')}"
|
162
|
+
candidate_node = nil
|
163
|
+
(0..@nodes.length).each do |_i|
|
164
|
+
@current_node_index = (@current_node_index + 1) % @nodes.length
|
165
|
+
candidate_node = @nodes[@current_node_index]
|
166
|
+
if candidate_node[:is_healthy] == true || node_due_for_healthcheck?(candidate_node)
|
167
|
+
@logger.debug "Updated current node to Node #{candidate_node[:index]}"
|
168
|
+
return candidate_node
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# None of the nodes are marked healthy, but some of them could have become healthy since last health check.
|
173
|
+
# So we will just return the next node.
|
174
|
+
@logger.debug "No healthy nodes were found. Returning the next node, Node #{candidate_node[:index]}"
|
175
|
+
candidate_node
|
176
|
+
end
|
177
|
+
|
178
|
+
def node_due_for_healthcheck?(node)
|
179
|
+
is_due_for_check = Time.now.to_i - node[:last_access_timestamp] > @healthcheck_interval_seconds
|
180
|
+
@logger.debug "Node #{node[:index]} has exceeded healthcheck_interval_seconds of #{@healthcheck_interval_seconds}. Adding it back into rotation." if is_due_for_check
|
181
|
+
is_due_for_check
|
182
|
+
end
|
183
|
+
|
184
|
+
def initialize_metadata_for_nodes
|
185
|
+
unless @nearest_node.nil?
|
186
|
+
@nearest_node[:index] = 'nearest_node'
|
187
|
+
set_node_healthcheck(@nearest_node, is_healthy: true)
|
188
|
+
end
|
189
|
+
@nodes.each_with_index do |node, index|
|
190
|
+
node[:index] = index
|
191
|
+
set_node_healthcheck(node, is_healthy: true)
|
192
|
+
end
|
193
|
+
end
|
99
194
|
|
100
|
-
|
195
|
+
def set_node_healthcheck(node, is_healthy:)
|
196
|
+
node[:is_healthy] = is_healthy
|
197
|
+
node[:last_access_timestamp] = Time.now.to_i
|
198
|
+
end
|
199
|
+
|
200
|
+
def custom_exception_klass_for(response)
|
201
|
+
response_code_type = response.code_type
|
202
|
+
if response_code_type <= Net::HTTPBadRequest # 400
|
203
|
+
Typesense::Error::RequestMalformed
|
204
|
+
elsif response_code_type <= Net::HTTPUnauthorized # 401
|
205
|
+
Typesense::Error::RequestUnauthorized
|
206
|
+
elsif response_code_type <= Net::HTTPNotFound # 404
|
207
|
+
Typesense::Error::ObjectNotFound
|
208
|
+
elsif response_code_type <= Net::HTTPConflict # 409
|
209
|
+
Typesense::Error::ObjectAlreadyExists
|
210
|
+
elsif response_code_type <= Net::HTTPUnprocessableEntity # 422
|
211
|
+
Typesense::Error::ObjectUnprocessable
|
212
|
+
elsif response_code_type <= Net::HTTPServerError # 5xx
|
213
|
+
Typesense::Error::ServerError
|
214
|
+
elsif response.code.to_i.zero?
|
215
|
+
Typesense::Error::HTTPStatus0Error
|
216
|
+
else
|
217
|
+
Typesense::Error::HTTPError
|
101
218
|
end
|
102
219
|
end
|
103
220
|
|
104
221
|
def default_options
|
105
222
|
{
|
106
|
-
timeout: @
|
223
|
+
timeout: @connection_timeout_seconds
|
107
224
|
}
|
108
225
|
end
|
109
226
|
|
110
227
|
def default_headers
|
111
228
|
{
|
112
|
-
API_KEY_HEADER_NAME.to_s => @
|
229
|
+
API_KEY_HEADER_NAME.to_s => @api_key
|
113
230
|
}
|
114
231
|
end
|
115
232
|
end
|