twelvedata_ruby 0.3.0 → 0.4.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/.rspec +4 -0
- data/.rubocop.yml +102 -0
- data/.yardopts +14 -0
- data/CHANGELOG.md +199 -4
- data/{LICENSE.txt → LICENSE} +1 -1
- data/README.md +392 -88
- data/Rakefile +110 -4
- data/lib/twelvedata_ruby/client.rb +137 -88
- data/lib/twelvedata_ruby/endpoint.rb +292 -242
- data/lib/twelvedata_ruby/error.rb +93 -45
- data/lib/twelvedata_ruby/request.rb +106 -33
- data/lib/twelvedata_ruby/response.rb +268 -110
- data/lib/twelvedata_ruby/utils.rb +91 -14
- data/lib/twelvedata_ruby/version.rb +6 -0
- data/lib/twelvedata_ruby.rb +41 -30
- data/twelvedata_ruby.gemspec +28 -23
- metadata +25 -143
- data/.gitignore +0 -13
- data/Gemfile +0 -6
- data/Gemfile.lock +0 -80
- data/bin/console +0 -22
- data/bin/setup +0 -8
@@ -1,156 +1,314 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "csv"
|
4
|
+
require "json"
|
5
|
+
require "tempfile"
|
6
|
+
|
7
|
+
# Handles API responses from Twelve Data
|
4
8
|
module TwelvedataRuby
|
5
9
|
class Response
|
6
|
-
|
7
|
-
|
8
|
-
HTTP_STATUSES = {http_error: (400..600), success: (200..299)}.freeze
|
9
|
-
CONTENT_TYPE_HANDLERS = {
|
10
|
-
json: {parser: :json_parser, dumper: :json_dumper},
|
11
|
-
csv: {parser: :csv_parser, dumper: :csv_dumper},
|
12
|
-
plain: {parser: :plain_parser, dumper: :to_s}
|
13
|
-
}.freeze
|
14
|
-
|
15
|
-
class << self
|
16
|
-
def resolve(http_response, request)
|
17
|
-
if http_status_codes.member?(http_response.status)
|
18
|
-
new(http_response: http_response, request: request)
|
19
|
-
else
|
20
|
-
resolve_error(http_response, request)
|
21
|
-
end
|
22
|
-
end
|
10
|
+
# CSV column separator used by Twelve Data
|
11
|
+
CSV_COL_SEP = ";"
|
23
12
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
13
|
+
# Maximum response body size to keep in memory
|
14
|
+
BODY_MAX_BYTESIZE = 16_000
|
15
|
+
|
16
|
+
# HTTP status code ranges
|
17
|
+
HTTP_STATUSES = {
|
18
|
+
http_error: (400..600),
|
19
|
+
success: (200..299),
|
20
|
+
}.freeze
|
21
|
+
|
22
|
+
# Content type handlers for different response formats
|
23
|
+
CONTENT_TYPE_HANDLERS = {
|
24
|
+
json: { parser: :parse_json, dumper: :dump_json },
|
25
|
+
csv: { parser: :parse_csv, dumper: :dump_csv },
|
26
|
+
plain: { parser: :parse_plain, dumper: :to_s },
|
27
|
+
}.freeze
|
32
28
|
|
33
|
-
|
34
|
-
|
29
|
+
class << self
|
30
|
+
# Resolve HTTP response into Response or ResponseError
|
31
|
+
#
|
32
|
+
# @param http_response [HTTPX::Response] HTTP response from client
|
33
|
+
# @param request [Request] Original request object
|
34
|
+
# @return [Response, ResponseError] Resolved response or error
|
35
|
+
def resolve(http_response, request)
|
36
|
+
if success_status?(http_response.status)
|
37
|
+
new(http_response: http_response, request: request)
|
38
|
+
else
|
39
|
+
create_error_from_response(http_response, request)
|
35
40
|
end
|
41
|
+
rescue StandardError => e
|
42
|
+
ResponseError.new(
|
43
|
+
message: "Failed to resolve response: #{e.message}",
|
44
|
+
request: request,
|
45
|
+
original_error: e,
|
46
|
+
)
|
36
47
|
end
|
37
48
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
49
|
+
# Get all valid HTTP status codes
|
50
|
+
#
|
51
|
+
# @return [Array<Integer>] Array of valid status codes
|
52
|
+
def valid_status_codes
|
53
|
+
@valid_status_codes ||= HTTP_STATUSES.values.flat_map(&:to_a)
|
42
54
|
end
|
43
55
|
|
44
|
-
|
45
|
-
return nil unless headers["content-disposition"]
|
56
|
+
private
|
46
57
|
|
47
|
-
|
58
|
+
def success_status?(status)
|
59
|
+
HTTP_STATUSES[:success].include?(status)
|
48
60
|
end
|
49
61
|
|
50
|
-
def
|
51
|
-
|
62
|
+
def create_error_from_response(http_response, request)
|
63
|
+
json_data = extract_error_data(http_response)
|
64
|
+
error_class = determine_error_class(http_response.status)
|
65
|
+
error_class.new(json_data:, request:, status_code: http_response.status, message: json_data[:message])
|
52
66
|
end
|
53
67
|
|
54
|
-
def
|
55
|
-
|
68
|
+
def extract_error_data(http_response)
|
69
|
+
if http_response.respond_to?(:error) && http_response.error
|
70
|
+
{
|
71
|
+
message: http_response.error.message,
|
72
|
+
code: http_response.error.class.name,
|
73
|
+
}
|
74
|
+
else
|
75
|
+
{
|
76
|
+
message: http_response.body.to_s,
|
77
|
+
code: http_response.status,
|
78
|
+
}
|
79
|
+
end
|
56
80
|
end
|
57
81
|
|
58
|
-
def
|
59
|
-
|
60
|
-
|
82
|
+
def determine_error_class(status_code)
|
83
|
+
error_class_name = ResponseError.error_class_for_code(status_code, :http) ||
|
84
|
+
ResponseError.error_class_for_code(status_code, :api)
|
61
85
|
|
62
|
-
|
63
|
-
|
86
|
+
if error_class_name
|
87
|
+
TwelvedataRuby.const_get(error_class_name)
|
88
|
+
else
|
89
|
+
ResponseError
|
90
|
+
end
|
64
91
|
end
|
92
|
+
end
|
65
93
|
|
66
|
-
|
67
|
-
@dumped_parsed_body ||=
|
68
|
-
parsed_body.respond_to?(parsed_body_dumper) ? parsed_body.send(parsed_body_dumper) : send(parsed_body_dumper)
|
69
|
-
end
|
94
|
+
attr_reader :http_response, :headers, :body_bytesize, :request
|
70
95
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
96
|
+
# Initialize response with HTTP response and request
|
97
|
+
#
|
98
|
+
# @param http_response [HTTPX::Response] HTTP response object
|
99
|
+
# @param request [Request] Original request object
|
100
|
+
def initialize(http_response:, request:)
|
101
|
+
@http_response = http_response
|
102
|
+
@request = request
|
103
|
+
@headers = http_response.headers
|
104
|
+
@body_bytesize = http_response.body.bytesize
|
105
|
+
@parsed_body = nil
|
106
|
+
end
|
77
107
|
|
78
|
-
|
79
|
-
|
80
|
-
|
108
|
+
# Get attachment filename from response headers
|
109
|
+
#
|
110
|
+
# @return [String, nil] Filename if present in headers
|
111
|
+
def attachment_filename
|
112
|
+
return nil unless headers["content-disposition"]
|
81
113
|
|
82
|
-
|
83
|
-
|
84
|
-
end
|
114
|
+
@attachment_filename ||= extract_filename_from_headers
|
115
|
+
end
|
85
116
|
|
86
|
-
|
87
|
-
|
88
|
-
|
117
|
+
# Get content type from response
|
118
|
+
#
|
119
|
+
# @return [Symbol, nil] Content type symbol (:json, :csv, :plain)
|
120
|
+
def content_type
|
121
|
+
@content_type ||= detect_content_type
|
122
|
+
end
|
89
123
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
else
|
98
|
-
tmp_file = Tempfile.new
|
99
|
-
http_response.body.copy_to(tmp_file)
|
100
|
-
@parsed_body = send(body_parser, IO.read(tmp_file.path))
|
101
|
-
end
|
102
|
-
ensure
|
103
|
-
http_response.body.close
|
104
|
-
tmp_file&.close
|
105
|
-
tmp_file&.unlink
|
106
|
-
end
|
124
|
+
# Get parsed response body
|
125
|
+
#
|
126
|
+
# @return [Hash, CSV::Table, String] Parsed response data
|
127
|
+
def parsed_body
|
128
|
+
@parsed_body ||= parse_response_body
|
129
|
+
end
|
130
|
+
alias body parsed_body
|
107
131
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
132
|
+
# Get response error if present
|
133
|
+
#
|
134
|
+
# @return [ResponseError, nil] Error object if response contains an error
|
135
|
+
def error
|
136
|
+
return nil unless parsed_body.is_a?(Hash) && parsed_body.key?(:code)
|
112
137
|
|
113
|
-
|
114
|
-
|
115
|
-
end
|
138
|
+
@error ||= create_response_error
|
139
|
+
end
|
116
140
|
|
117
|
-
|
118
|
-
|
119
|
-
|
141
|
+
# Get HTTP status code
|
142
|
+
#
|
143
|
+
# @return [Integer] HTTP status code
|
144
|
+
def http_status_code
|
145
|
+
http_response.status
|
146
|
+
end
|
147
|
+
|
148
|
+
# Get API status code (from response body)
|
149
|
+
#
|
150
|
+
# @return [Integer] API status code
|
151
|
+
def status_code
|
152
|
+
@status_code ||= extract_status_code
|
153
|
+
end
|
154
|
+
|
155
|
+
# Check if HTTP response was successful
|
156
|
+
#
|
157
|
+
# @return [Boolean] True if HTTP status indicates success
|
158
|
+
def success?
|
159
|
+
HTTP_STATUSES[:success].include?(http_status_code)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Save response to disk file
|
163
|
+
#
|
164
|
+
# @param file_path [String] Path to save file (defaults to attachment filename)
|
165
|
+
# @return [File, nil] File object if successful, nil otherwise
|
166
|
+
def save_to_file(file_path = nil)
|
167
|
+
file_path ||= attachment_filename
|
168
|
+
return nil unless file_path
|
120
169
|
|
121
|
-
|
122
|
-
|
170
|
+
File.open(file_path, "w") do |file|
|
171
|
+
file.write(dump_parsed_body)
|
123
172
|
end
|
173
|
+
rescue StandardError => e
|
174
|
+
raise ResponseError.new(
|
175
|
+
message: "Failed to save response to file: #{e.message}",
|
176
|
+
original_error: e,
|
177
|
+
)
|
178
|
+
end
|
179
|
+
|
180
|
+
# Get dumped (serialized) version of parsed body
|
181
|
+
#
|
182
|
+
# @return [String] Serialized response body
|
183
|
+
def dump_parsed_body
|
184
|
+
handler = CONTENT_TYPE_HANDLERS[content_type]
|
185
|
+
return parsed_body.to_s unless handler
|
124
186
|
|
125
|
-
|
126
|
-
|
187
|
+
send(handler[:dumper])
|
188
|
+
end
|
189
|
+
|
190
|
+
# String representation of response
|
191
|
+
#
|
192
|
+
# @return [String] Response summary
|
193
|
+
def to_s
|
194
|
+
status_info = "#{http_status_code} (#{success? ? "success" : "error"})"
|
195
|
+
"Response: #{status_info}, Content-Type: #{content_type}, Size: #{body_bytesize} bytes"
|
196
|
+
end
|
197
|
+
|
198
|
+
# Detailed inspection of response
|
199
|
+
#
|
200
|
+
# @return [String] Detailed response information
|
201
|
+
def inspect
|
202
|
+
"#<#{self.class.name}:#{object_id} status=#{http_status_code} content_type=#{content_type} " \
|
203
|
+
"size=#{body_bytesize} error=#{error ? "yes" : "no"}>"
|
204
|
+
end
|
205
|
+
|
206
|
+
private
|
207
|
+
|
208
|
+
def extract_filename_from_headers
|
209
|
+
disposition = headers["content-disposition"]
|
210
|
+
return nil unless disposition
|
211
|
+
|
212
|
+
# Extract filename from Content-Disposition header
|
213
|
+
match = disposition.match(/filename="([^"]+)"/)
|
214
|
+
match ? match[1] : nil
|
215
|
+
end
|
216
|
+
|
217
|
+
def detect_content_type
|
218
|
+
content_type_header = headers["content-type"]
|
219
|
+
return :plain unless content_type_header
|
220
|
+
|
221
|
+
case content_type_header
|
222
|
+
when /json/
|
223
|
+
:json
|
224
|
+
when /csv/
|
225
|
+
:csv
|
226
|
+
else
|
227
|
+
:plain
|
127
228
|
end
|
229
|
+
end
|
128
230
|
|
129
|
-
|
130
|
-
|
231
|
+
def parse_response_body
|
232
|
+
return nil unless http_response.body
|
131
233
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
file&.close
|
234
|
+
begin
|
235
|
+
if body_bytesize < BODY_MAX_BYTESIZE
|
236
|
+
parse_body_content(http_response.body.to_s)
|
237
|
+
else
|
238
|
+
parse_large_body_content
|
138
239
|
end
|
240
|
+
ensure
|
241
|
+
http_response.body.close if http_response.body.respond_to?(:close)
|
139
242
|
end
|
243
|
+
end
|
140
244
|
|
141
|
-
|
245
|
+
def parse_body_content(content)
|
246
|
+
handler = CONTENT_TYPE_HANDLERS[content_type]
|
247
|
+
return content unless handler
|
142
248
|
|
143
|
-
|
249
|
+
send(handler[:parser], content)
|
250
|
+
end
|
144
251
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
252
|
+
def parse_large_body_content
|
253
|
+
Tempfile.create do |temp_file|
|
254
|
+
http_response.body.copy_to(temp_file)
|
255
|
+
temp_file.rewind
|
256
|
+
parse_body_content(temp_file.read)
|
150
257
|
end
|
258
|
+
end
|
151
259
|
|
152
|
-
|
153
|
-
|
260
|
+
def parse_json(content)
|
261
|
+
JSON.parse(content, symbolize_names: true)
|
262
|
+
rescue JSON::ParserError => e
|
263
|
+
raise ResponseError.new(
|
264
|
+
message: "Failed to parse JSON response: #{e.message}",
|
265
|
+
original_error: e,
|
266
|
+
)
|
267
|
+
end
|
268
|
+
|
269
|
+
def parse_csv(content)
|
270
|
+
CSV.parse(content, headers: true, col_sep: CSV_COL_SEP)
|
271
|
+
rescue CSV::MalformedCSVError => e
|
272
|
+
raise ResponseError.new(
|
273
|
+
message: "Failed to parse CSV response: #{e.message}",
|
274
|
+
original_error: e,
|
275
|
+
)
|
276
|
+
end
|
277
|
+
|
278
|
+
def parse_plain(content)
|
279
|
+
content.to_s
|
280
|
+
end
|
281
|
+
|
282
|
+
def dump_json
|
283
|
+
return nil unless parsed_body.is_a?(Hash)
|
284
|
+
|
285
|
+
JSON.dump(parsed_body)
|
286
|
+
end
|
287
|
+
|
288
|
+
def dump_csv
|
289
|
+
return nil unless parsed_body.is_a?(CSV::Table)
|
290
|
+
|
291
|
+
parsed_body.to_csv(col_sep: CSV_COL_SEP)
|
292
|
+
end
|
293
|
+
|
294
|
+
def extract_status_code
|
295
|
+
if parsed_body.is_a?(Hash) && parsed_body[:code]
|
296
|
+
parsed_body[:code]
|
297
|
+
else
|
298
|
+
http_status_code
|
154
299
|
end
|
155
300
|
end
|
301
|
+
|
302
|
+
def create_response_error
|
303
|
+
error_class_name = ResponseError.error_class_for_code(status_code)
|
304
|
+
error_class = error_class_name ? TwelvedataRuby.const_get(error_class_name) : ResponseError
|
305
|
+
|
306
|
+
error_class.new(
|
307
|
+
json_data: parsed_body,
|
308
|
+
request: request,
|
309
|
+
status_code: status_code,
|
310
|
+
message: parsed_body[:message],
|
311
|
+
)
|
312
|
+
end
|
313
|
+
end
|
156
314
|
end
|
@@ -1,36 +1,113 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# Utility methods for common operations
|
3
4
|
module TwelvedataRuby
|
4
5
|
module Utils
|
5
|
-
|
6
|
+
class << self
|
7
|
+
# Removes module namespace from class name
|
8
|
+
#
|
9
|
+
# @param obj [Object] Object to extract class name from
|
10
|
+
# @return [String] Class name without module namespace
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# Utils.demodulize(TwelvedataRuby::Error) #=> "Error"
|
14
|
+
def demodulize(obj)
|
6
15
|
obj.to_s.gsub(/^.+::/, "")
|
7
16
|
end
|
8
17
|
|
9
|
-
# Converts
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
18
|
+
# Converts string to integer with default fallback
|
19
|
+
#
|
20
|
+
# @param obj [Object] Object to convert
|
21
|
+
# @param default_value [Integer, nil] Default value if conversion fails
|
22
|
+
# @return [Integer, nil] Converted integer or default value
|
23
|
+
#
|
24
|
+
# @example
|
25
|
+
# Utils.to_integer("123") #=> 123
|
26
|
+
# Utils.to_integer("abc", 0) #=> 0
|
27
|
+
def to_integer(obj, default_value = nil)
|
28
|
+
obj.is_a?(Integer) ? obj : Integer(obj.to_s)
|
29
|
+
rescue ArgumentError
|
30
|
+
default_value
|
14
31
|
end
|
15
32
|
|
16
|
-
|
33
|
+
# Converts snake_case to CamelCase
|
34
|
+
#
|
35
|
+
# @param str [String] String to convert
|
36
|
+
# @return [String] CamelCase string
|
37
|
+
#
|
38
|
+
# @example
|
39
|
+
# Utils.camelize("snake_case") #=> "SnakeCase"
|
40
|
+
def camelize(str)
|
17
41
|
str.to_s.split("_").map(&:capitalize).join
|
18
42
|
end
|
19
43
|
|
20
|
-
|
21
|
-
|
44
|
+
# Converts empty values to nil
|
45
|
+
#
|
46
|
+
# @param obj [Object] Object to check
|
47
|
+
# @return [Object, nil] Original object or nil if empty
|
48
|
+
#
|
49
|
+
# @example
|
50
|
+
# Utils.empty_to_nil("") #=> nil
|
51
|
+
# Utils.empty_to_nil("test") #=> "test"
|
52
|
+
def empty_to_nil(obj)
|
53
|
+
return nil if obj.nil?
|
54
|
+
return nil if obj.respond_to?(:empty?) && obj.empty?
|
55
|
+
|
56
|
+
obj
|
22
57
|
end
|
23
58
|
|
24
|
-
|
59
|
+
# Ensures return value is an array
|
60
|
+
#
|
61
|
+
# @param objects [Object] Single object or array
|
62
|
+
# @return [Array] Array containing the objects
|
63
|
+
#
|
64
|
+
# @example
|
65
|
+
# Utils.to_array("test") #=> ["test"]
|
66
|
+
# Utils.to_array(["a", "b"]) #=> ["a", "b"]
|
67
|
+
def to_array(objects)
|
25
68
|
objects.is_a?(Array) ? objects : [objects]
|
26
69
|
end
|
27
70
|
|
28
|
-
|
29
|
-
|
71
|
+
# Executes block if condition is truthy
|
72
|
+
#
|
73
|
+
# @param condition [Object] Condition to evaluate
|
74
|
+
# @param default_return [Object] Default return value
|
75
|
+
# @yield Block to execute if condition is truthy
|
76
|
+
# @return [Object] Block result or default value
|
77
|
+
def execute_if_truthy(condition, default_return = nil)
|
78
|
+
return default_return unless condition && block_given?
|
79
|
+
|
80
|
+
yield
|
81
|
+
end
|
82
|
+
|
83
|
+
# Executes block only if condition is exactly true
|
84
|
+
#
|
85
|
+
# @param condition [Object] Condition to evaluate
|
86
|
+
# @yield Block to execute if condition is true
|
87
|
+
# @return [Object, nil] Block result or nil
|
88
|
+
def execute_if_true(condition, &block)
|
89
|
+
execute_if_truthy(condition == true, &block)
|
30
90
|
end
|
31
91
|
|
32
|
-
|
33
|
-
|
92
|
+
# Validates that a value is not blank
|
93
|
+
#
|
94
|
+
# @param value [Object] Value to validate
|
95
|
+
# @return [Boolean] True if value is present
|
96
|
+
def present?(value)
|
97
|
+
!blank?(value)
|
34
98
|
end
|
99
|
+
|
100
|
+
# Checks if a value is blank (nil, empty, or whitespace-only)
|
101
|
+
#
|
102
|
+
# @param value [Object] Value to check
|
103
|
+
# @return [Boolean] True if value is blank
|
104
|
+
def blank?(value)
|
105
|
+
return true if value.nil?
|
106
|
+
return true if value.respond_to?(:empty?) && value.empty?
|
107
|
+
return true if value.is_a?(String) && value.strip.empty?
|
108
|
+
|
109
|
+
false
|
110
|
+
end
|
111
|
+
end
|
35
112
|
end
|
36
113
|
end
|
data/lib/twelvedata_ruby.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "twelvedata_ruby/version"
|
3
4
|
require_relative "twelvedata_ruby/utils"
|
4
5
|
require_relative "twelvedata_ruby/error"
|
5
6
|
require_relative "twelvedata_ruby/endpoint"
|
@@ -7,37 +8,47 @@ require_relative "twelvedata_ruby/request"
|
|
7
8
|
require_relative "twelvedata_ruby/response"
|
8
9
|
require_relative "twelvedata_ruby/client"
|
9
10
|
|
10
|
-
#
|
11
|
-
|
11
|
+
# TwelvedataRuby provides a Ruby interface for accessing Twelve Data's financial API
|
12
|
+
#
|
13
|
+
# @example Basic usage
|
14
|
+
# client = TwelvedataRuby.client(apikey: "your-api-key")
|
15
|
+
# response = client.quote(symbol: "AAPL")
|
16
|
+
# puts response.parsed_body
|
17
|
+
#
|
18
|
+
# @example Using environment variable for API key
|
19
|
+
# ENV['TWELVEDATA_API_KEY'] = 'your-api-key'
|
20
|
+
# client = TwelvedataRuby.client
|
21
|
+
# response = client.price(symbol: "GOOGL")
|
12
22
|
module TwelvedataRuby
|
13
|
-
|
14
|
-
|
15
|
-
|
23
|
+
class << self
|
24
|
+
# Creates and configures a client instance
|
25
|
+
#
|
26
|
+
# @param options [Hash] Configuration options
|
27
|
+
# @option options [String] :apikey The Twelve Data API key
|
28
|
+
# @option options [Integer] :connect_timeout Connection timeout in milliseconds
|
29
|
+
# @option options [String] :apikey_env_var_name Environment variable name for API key
|
30
|
+
#
|
31
|
+
# @return [Client] Configured client instance
|
32
|
+
#
|
33
|
+
# @example Basic client creation
|
34
|
+
# client = TwelvedataRuby.client(apikey: "your-key")
|
35
|
+
#
|
36
|
+
# @example With custom timeout
|
37
|
+
# client = TwelvedataRuby.client(
|
38
|
+
# apikey: "your-key",
|
39
|
+
# connect_timeout: 5000
|
40
|
+
# )
|
41
|
+
def client(**options)
|
42
|
+
client_instance = Client.instance
|
43
|
+
client_instance.configure(**options) if options.any?
|
44
|
+
client_instance
|
45
|
+
end
|
16
46
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
# @example Passing a nil options
|
24
|
-
# TwelvedataRuby.client
|
25
|
-
#
|
26
|
-
# The singleton instance object returned will use the default values for its attributes
|
27
|
-
#
|
28
|
-
# @example Passing values of `:apikey` and `:connect_timeout`
|
29
|
-
# TwelvedataRuby.client(apikey: "my-twelvedata-apikey", connect_timeout: 3000)
|
30
|
-
#
|
31
|
-
# @example or, chain with other Client instance method
|
32
|
-
# TwelvedataRuby.client(apikey: "my-twelvedata-apikey", connect_timeout: 3000).quote(symbol: "IBM")
|
33
|
-
#
|
34
|
-
# In the last example, calling `#quote`, a valid API endpoint, an instance method with the same name
|
35
|
-
# was dynamically defined and then fired up an API request to Twelvedata.
|
36
|
-
#
|
37
|
-
# @return [Client] singleton instance
|
38
|
-
def self.client(**options)
|
39
|
-
client = Client.instance
|
40
|
-
client.options = (client.options || {}).merge(options)
|
41
|
-
client
|
47
|
+
# Returns the current version
|
48
|
+
#
|
49
|
+
# @return [String] Version string
|
50
|
+
def version
|
51
|
+
VERSION
|
52
|
+
end
|
42
53
|
end
|
43
54
|
end
|