lago-ruby-client 1.45.0 → 1.46.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/lib/lago/api/client.rb +4 -2
- data/lib/lago/api/connection.rb +115 -53
- data/lib/lago/api/rate_limit_error.rb +23 -0
- data/lib/lago/api/resources/base.rb +6 -1
- data/lib/lago/api/resources/event.rb +6 -1
- data/lib/lago/api/resources/nested.rb +6 -1
- data/lib/lago/version.rb +1 -1
- data/lib/lago-ruby-client.rb +1 -0
- metadata +32 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b0d737a1ba08ae4f9d2a731af4cf55918555cfe91e1606fbef6126b830090e82
|
|
4
|
+
data.tar.gz: 06b6d2a32667abe5872075cb96081aedff4d0d07fa49e0a4158f594ee66f4803
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fbb748d95cb04580ecba3e5340e5587c72f4b20ce629fefc06eeb8435c074cbde11e8874407bcdf3f0f32680e961ca553b1b6ae929d695357e2bf2d21ca13041
|
|
7
|
+
data.tar.gz: a0ef70eeb95aff16cef4492c9b2ba989323b7bae8cbc26b50a9cb3e098cd49ea54bee30fc83e6f4912f226eb5bfb538a3bb3e3f3c6829ec592b2e50bee474778
|
data/lib/lago/api/client.rb
CHANGED
|
@@ -53,13 +53,15 @@ module Lago
|
|
|
53
53
|
API_PATH = 'api/v1/'
|
|
54
54
|
|
|
55
55
|
class Client
|
|
56
|
-
attr_reader :api_key, :api_url, :use_ingest_service, :ingest_api_url
|
|
56
|
+
attr_reader :api_key, :api_url, :use_ingest_service, :ingest_api_url, :max_retries, :retry_on_rate_limit
|
|
57
57
|
|
|
58
|
-
def initialize(api_key: nil, api_url: nil, use_ingest_service: false, ingest_api_url: nil)
|
|
58
|
+
def initialize(api_key: nil, api_url: nil, use_ingest_service: false, ingest_api_url: nil, **options)
|
|
59
59
|
@api_key = api_key
|
|
60
60
|
@api_url = api_url
|
|
61
61
|
@use_ingest_service = use_ingest_service
|
|
62
62
|
@ingest_api_url = ingest_api_url
|
|
63
|
+
@max_retries = options.fetch(:max_retries, 3)
|
|
64
|
+
@retry_on_rate_limit = options.fetch(:retry_on_rate_limit, true)
|
|
63
65
|
end
|
|
64
66
|
|
|
65
67
|
def base_api_url
|
data/lib/lago/api/connection.rb
CHANGED
|
@@ -6,84 +6,90 @@ module Lago
|
|
|
6
6
|
module Api
|
|
7
7
|
class Connection
|
|
8
8
|
RESPONSE_SUCCESS_CODES = [200, 201, 202, 204].freeze
|
|
9
|
+
DEFAULT_MAX_RETRIES = 3
|
|
10
|
+
INITIAL_BACKOFF = 1
|
|
11
|
+
BACKOFF_MULTIPLIER = 2
|
|
12
|
+
MAX_RETRY_DELAY = 20
|
|
9
13
|
|
|
10
|
-
def initialize(api_key, uri)
|
|
14
|
+
def initialize(api_key, uri, max_retries: DEFAULT_MAX_RETRIES, retry_on_rate_limit: true)
|
|
11
15
|
@api_key = api_key
|
|
12
16
|
@uri = uri
|
|
17
|
+
@max_retries = max_retries
|
|
18
|
+
@retry_on_rate_limit = retry_on_rate_limit
|
|
13
19
|
end
|
|
14
20
|
|
|
15
21
|
def post(body, path = uri.path)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
execute_request do
|
|
23
|
+
http_client.send_request(
|
|
24
|
+
'POST',
|
|
25
|
+
path,
|
|
26
|
+
prepare_payload(body),
|
|
27
|
+
headers
|
|
28
|
+
)
|
|
29
|
+
end
|
|
24
30
|
end
|
|
25
31
|
|
|
26
32
|
def put(path = uri.path, identifier:, body:)
|
|
27
33
|
uri_path = identifier.nil? ? path : "#{path}/#{CGI.escapeURIComponent(identifier)}"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
execute_request do
|
|
35
|
+
http_client.send_request(
|
|
36
|
+
'PUT',
|
|
37
|
+
uri_path,
|
|
38
|
+
prepare_payload(body),
|
|
39
|
+
headers
|
|
40
|
+
)
|
|
41
|
+
end
|
|
36
42
|
end
|
|
37
43
|
|
|
38
44
|
def patch(path = uri.path, identifier:, body:)
|
|
39
45
|
uri_path = identifier.nil? ? path : "#{path}/#{CGI.escapeURIComponent(identifier)}"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
execute_request do
|
|
47
|
+
http_client.send_request(
|
|
48
|
+
'PATCH',
|
|
49
|
+
uri_path,
|
|
50
|
+
prepare_payload(body),
|
|
51
|
+
headers
|
|
52
|
+
)
|
|
53
|
+
end
|
|
48
54
|
end
|
|
49
55
|
|
|
50
56
|
def get(path = uri.path, identifier:)
|
|
51
57
|
uri_path = identifier.nil? ? path : "#{path}/#{CGI.escapeURIComponent(identifier)}"
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
execute_request do
|
|
59
|
+
http_client.send_request(
|
|
60
|
+
'GET',
|
|
61
|
+
uri_path,
|
|
62
|
+
prepare_payload(nil),
|
|
63
|
+
headers
|
|
64
|
+
)
|
|
65
|
+
end
|
|
60
66
|
end
|
|
61
67
|
|
|
62
68
|
def destroy(path = uri.path, identifier:, options: nil)
|
|
63
69
|
uri_path = path
|
|
64
70
|
uri_path += "/#{CGI.escapeURIComponent(identifier)}" if identifier
|
|
65
71
|
uri_path += "?#{URI.encode_www_form(options)}" unless options.nil?
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
execute_request do
|
|
73
|
+
http_client.send_request(
|
|
74
|
+
'DELETE',
|
|
75
|
+
uri_path,
|
|
76
|
+
prepare_payload(nil),
|
|
77
|
+
headers
|
|
78
|
+
)
|
|
79
|
+
end
|
|
74
80
|
end
|
|
75
81
|
|
|
76
82
|
def get_all(options, path = uri.path)
|
|
77
83
|
uri_path = options.empty? ? path : "#{path}?#{URI.encode_www_form(options)}"
|
|
78
84
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
execute_request do
|
|
86
|
+
http_client.send_request(
|
|
87
|
+
'GET',
|
|
88
|
+
uri_path,
|
|
89
|
+
prepare_payload(nil),
|
|
90
|
+
headers
|
|
91
|
+
)
|
|
92
|
+
end
|
|
87
93
|
end
|
|
88
94
|
|
|
89
95
|
private
|
|
@@ -98,14 +104,35 @@ module Lago
|
|
|
98
104
|
}
|
|
99
105
|
end
|
|
100
106
|
|
|
101
|
-
def
|
|
102
|
-
|
|
107
|
+
def execute_request(retry_count = 0, &block)
|
|
108
|
+
response = yield
|
|
109
|
+
handle_response(response, retry_count, block)
|
|
110
|
+
end
|
|
103
111
|
|
|
104
|
-
|
|
112
|
+
def handle_response(response, retry_count, block)
|
|
113
|
+
code = response.code.to_i
|
|
114
|
+
|
|
115
|
+
if code == 429 && @retry_on_rate_limit && retry_count < @max_retries
|
|
116
|
+
handle_rate_limit(response, retry_count, block)
|
|
117
|
+
elsif !RESPONSE_SUCCESS_CODES.include?(code)
|
|
118
|
+
raise_error(response)
|
|
119
|
+
else
|
|
120
|
+
parse_response_body(response)
|
|
121
|
+
end
|
|
105
122
|
rescue JSON::ParserError
|
|
106
123
|
response.body
|
|
107
124
|
end
|
|
108
125
|
|
|
126
|
+
def handle_rate_limit(response, retry_count, block)
|
|
127
|
+
reset_seconds = extract_reset_seconds(response, retry_count)
|
|
128
|
+
sleep(reset_seconds)
|
|
129
|
+
execute_request(retry_count + 1, &block)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def parse_response_body(response)
|
|
133
|
+
response.body.empty? || JSON.parse(response.body)
|
|
134
|
+
end
|
|
135
|
+
|
|
109
136
|
def http_client
|
|
110
137
|
http_client = Net::HTTP.new(uri.hostname, uri.port)
|
|
111
138
|
http_client.use_ssl = true if uri.scheme == 'https'
|
|
@@ -120,7 +147,42 @@ module Lago
|
|
|
120
147
|
end
|
|
121
148
|
|
|
122
149
|
def raise_error(response)
|
|
123
|
-
|
|
150
|
+
code = response.code.to_i
|
|
151
|
+
|
|
152
|
+
raise Lago::Api::HttpError.new(code, response.body, uri) unless code == 429
|
|
153
|
+
|
|
154
|
+
limit, remaining, reset = parse_rate_limit_headers(response)
|
|
155
|
+
raise Lago::Api::RateLimitError.new(
|
|
156
|
+
code,
|
|
157
|
+
response.body,
|
|
158
|
+
uri,
|
|
159
|
+
limit:,
|
|
160
|
+
remaining:,
|
|
161
|
+
reset:
|
|
162
|
+
)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def parse_rate_limit_headers(response)
|
|
166
|
+
limit = response['x-ratelimit-limit']&.to_i
|
|
167
|
+
remaining = response['x-ratelimit-remaining']&.to_i
|
|
168
|
+
reset = response['x-ratelimit-reset']&.to_i
|
|
169
|
+
|
|
170
|
+
[limit, remaining, reset]
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def extract_reset_seconds(response, retry_count)
|
|
174
|
+
delay = if response['x-ratelimit-reset']
|
|
175
|
+
[response['x-ratelimit-reset'].to_i, INITIAL_BACKOFF].max
|
|
176
|
+
else
|
|
177
|
+
# Exponential backoff if header is missing
|
|
178
|
+
calculate_backoff(retry_count)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
[delay, MAX_RETRY_DELAY].min
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def calculate_backoff(retry_count)
|
|
185
|
+
INITIAL_BACKOFF * (BACKOFF_MULTIPLIER**retry_count)
|
|
124
186
|
end
|
|
125
187
|
end
|
|
126
188
|
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lago
|
|
4
|
+
module Api
|
|
5
|
+
class RateLimitError < HttpError
|
|
6
|
+
attr_reader :limit, :remaining, :reset
|
|
7
|
+
|
|
8
|
+
def initialize(code, body, uri, **options)
|
|
9
|
+
super(code, body, uri)
|
|
10
|
+
@limit = options[:limit]
|
|
11
|
+
@remaining = options[:remaining]
|
|
12
|
+
@reset = options[:reset]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def message
|
|
16
|
+
base_message = "HTTP #{error_code} - URI: #{uri}.\nError: #{error_body}"
|
|
17
|
+
return base_message unless reset
|
|
18
|
+
|
|
19
|
+
"#{base_message}\nRate limit will reset in #{reset} seconds."
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -59,7 +59,12 @@ module Lago
|
|
|
59
59
|
def connection
|
|
60
60
|
uri = URI.join(client.base_api_url, api_resource)
|
|
61
61
|
|
|
62
|
-
Lago::Api::Connection.new(
|
|
62
|
+
Lago::Api::Connection.new(
|
|
63
|
+
client.api_key,
|
|
64
|
+
uri,
|
|
65
|
+
max_retries: client.max_retries,
|
|
66
|
+
retry_on_rate_limit: client.retry_on_rate_limit,
|
|
67
|
+
)
|
|
63
68
|
end
|
|
64
69
|
end
|
|
65
70
|
end
|
|
@@ -16,7 +16,12 @@ module Lago
|
|
|
16
16
|
|
|
17
17
|
def create(params)
|
|
18
18
|
uri = URI("#{client.base_ingest_api_url}#{api_resource}")
|
|
19
|
-
connection = Lago::Api::Connection.new(
|
|
19
|
+
connection = Lago::Api::Connection.new(
|
|
20
|
+
client.api_key,
|
|
21
|
+
uri,
|
|
22
|
+
max_retries: client.max_retries,
|
|
23
|
+
retry_on_rate_limit: client.retry_on_rate_limit,
|
|
24
|
+
)
|
|
20
25
|
|
|
21
26
|
payload = whitelist_params(params)
|
|
22
27
|
response = connection.post(payload, uri)[root_name]
|
|
@@ -8,7 +8,12 @@ module Lago
|
|
|
8
8
|
class Nested < Base
|
|
9
9
|
def initialize(client)
|
|
10
10
|
super(client)
|
|
11
|
-
@connection = Lago::Api::Connection.new(
|
|
11
|
+
@connection = Lago::Api::Connection.new(
|
|
12
|
+
client.api_key,
|
|
13
|
+
client.base_api_url,
|
|
14
|
+
max_retries: client.max_retries,
|
|
15
|
+
retry_on_rate_limit: client.retry_on_rate_limit,
|
|
16
|
+
)
|
|
12
17
|
end
|
|
13
18
|
|
|
14
19
|
def create(*parent_ids, params)
|
data/lib/lago/version.rb
CHANGED
data/lib/lago-ruby-client.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lago-ruby-client
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.46.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Lovro Colic
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: jwt
|
|
@@ -52,6 +51,20 @@ dependencies:
|
|
|
52
51
|
- - ">="
|
|
53
52
|
- !ruby/object:Gem::Version
|
|
54
53
|
version: '0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: benchmark
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
55
68
|
- !ruby/object:Gem::Dependency
|
|
56
69
|
name: bigdecimal
|
|
57
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -192,6 +205,20 @@ dependencies:
|
|
|
192
205
|
- - "~>"
|
|
193
206
|
- !ruby/object:Gem::Version
|
|
194
207
|
version: '13.0'
|
|
208
|
+
- !ruby/object:Gem::Dependency
|
|
209
|
+
name: readline
|
|
210
|
+
requirement: !ruby/object:Gem::Requirement
|
|
211
|
+
requirements:
|
|
212
|
+
- - ">="
|
|
213
|
+
- !ruby/object:Gem::Version
|
|
214
|
+
version: '0'
|
|
215
|
+
type: :development
|
|
216
|
+
prerelease: false
|
|
217
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
218
|
+
requirements:
|
|
219
|
+
- - ">="
|
|
220
|
+
- !ruby/object:Gem::Version
|
|
221
|
+
version: '0'
|
|
195
222
|
- !ruby/object:Gem::Dependency
|
|
196
223
|
name: rspec
|
|
197
224
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -276,7 +303,6 @@ dependencies:
|
|
|
276
303
|
- - ">="
|
|
277
304
|
- !ruby/object:Gem::Version
|
|
278
305
|
version: '0'
|
|
279
|
-
description:
|
|
280
306
|
email:
|
|
281
307
|
- lovro@getlago.com
|
|
282
308
|
executables: []
|
|
@@ -287,6 +313,7 @@ files:
|
|
|
287
313
|
- lib/lago/api/client.rb
|
|
288
314
|
- lib/lago/api/connection.rb
|
|
289
315
|
- lib/lago/api/http_error.rb
|
|
316
|
+
- lib/lago/api/rate_limit_error.rb
|
|
290
317
|
- lib/lago/api/resources/activity_log.rb
|
|
291
318
|
- lib/lago/api/resources/add_on.rb
|
|
292
319
|
- lib/lago/api/resources/api_log.rb
|
|
@@ -341,7 +368,6 @@ metadata:
|
|
|
341
368
|
homepage_uri: https://www.getlago.com/
|
|
342
369
|
source_code_uri: https://github.com/getlago/lago-ruby-client
|
|
343
370
|
documentation_uri: https://doc.getlago.com
|
|
344
|
-
post_install_message:
|
|
345
371
|
rdoc_options: []
|
|
346
372
|
require_paths:
|
|
347
373
|
- lib
|
|
@@ -356,8 +382,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
356
382
|
- !ruby/object:Gem::Version
|
|
357
383
|
version: '0'
|
|
358
384
|
requirements: []
|
|
359
|
-
rubygems_version:
|
|
360
|
-
signing_key:
|
|
385
|
+
rubygems_version: 4.0.6
|
|
361
386
|
specification_version: 4
|
|
362
387
|
summary: Lago Rest API client
|
|
363
388
|
test_files: []
|