fulfil-io 0.7.0 → 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: 63d67b77df97810e72d56c2cd60a859004ee97de438bd11332cb643c8b8d0466
4
- data.tar.gz: 82708a84ce96c3254236ab4ce7e05a9d64477dc12b94ebb47d758ad267cc818e
3
+ metadata.gz: 5e82498f50af6f27434982cbeb6bb3faec54a085e999f4d86293d4378afb801a
4
+ data.tar.gz: d2596d9afedaf2f67aa3f573c139d1c5a03b706e638fdf2407fc482f25336d6f
5
5
  SHA512:
6
- metadata.gz: 609049e9604f06602526be98da080e33bdbc1a1d5a46e4d338cc5a86cf5f9df9584963b6462e30da5d493b354bb73ca7c489d1c9229b37230ef7654af821cc43
7
- data.tar.gz: f27513ccae674a68a294e12aacecf82c92a2caa6d65077a6abc7497c41703b7b689a4cb2a1b421a9b65c95584243d5a9d95b2f33b11571ec76eba009005cce13
6
+ metadata.gz: 67ad033ab2a666abc04747a37e1d4859690158cfdf1c6455cdc3e3383ccfd6aa74b556def1b02e7584945120b426357ed4a85fa1f1feaa74ad118fbe6161280c
7
+ data.tar.gz: 1ade1a109a650b56c76fcaea49ed71d92b87d8f87e6659748ea5c739d6e6f1715ec6099835f2faefd7c515e8482054d580c17b0fcb74342c2cc214d40fabaeee
data/README.md CHANGED
@@ -1,4 +1,6 @@
1
1
  [![Tests](https://github.com/knowndecimal/fulfil/actions/workflows/tests.yml/badge.svg)](https://github.com/knowndecimal/fulfil/actions/workflows/tests.yml)
2
+ [![Maintainability](https://api.codeclimate.com/v1/badges/c6100940d3debd3a3a7c/maintainability)](https://codeclimate.com/github/knowndecimal/fulfil/maintainability)
3
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/c6100940d3debd3a3a7c/test_coverage)](https://codeclimate.com/github/knowndecimal/fulfil/test_coverage)
2
4
 
3
5
  # Fulfil.io Rubygem
4
6
 
@@ -146,6 +148,8 @@ $ Fulfil.rate_limit.resets_at
146
148
  => #<DateTime: 2022-01-21T16:36:01-04:00 />
147
149
  ```
148
150
 
151
+ ### Automatic retry API call after rate limit hit
152
+
149
153
  Automatic retries are supported whenever the rate limit is reached. However, it's not enabled by default. To enable it, set the `retry_on_rate_limit` to `true`. By default, the request will be retried in 1 second.
150
154
 
151
155
  ```ruby
@@ -157,6 +161,32 @@ Fulfil.configure do |config|
157
161
  end
158
162
  ```
159
163
 
164
+ ### Monitor rate limit hits
165
+
166
+ Through the configurable `rate_limit_notification_handler` one can monitor the rate limit hits to the APM tool of choice.
167
+
168
+ ```ruby
169
+ # config/initializers/fulfil.rb
170
+
171
+ Fulfil.configure do |config|
172
+ config.rate_limit_notification_handler = proc {
173
+ FakeAPM.increment_counter('fulfil.rate_limit_exceeded')
174
+ }
175
+ end
176
+ ```
177
+
178
+ ## Retrieve multiple records
179
+
180
+ To retrieve multiple records at once, one can pass the IDs into the find method directly.
181
+
182
+ ```ruby
183
+ FulfilClient.find(
184
+ model: 'sale.sale',
185
+ ids: [1, 2, 3, 4],
186
+ fields: %w[id status]
187
+ )
188
+ ```
189
+
160
190
  ## Development
161
191
 
162
192
  After checking out the repo, run `bin/setup` to install dependencies. Then, run
@@ -197,7 +227,7 @@ def test_find_one
197
227
  stub_fulfil_get('sale.sale/213112', 'sale_sale')
198
228
 
199
229
  client = Fulfil::Client.new
200
- response = client.find_one(model: 'sale.sale', id: 213_112)
230
+ response = client.find(model: 'sale.sale', id: 213_112)
201
231
 
202
232
  assert_equal 213_112, response['id']
203
233
  end
data/Rakefile CHANGED
@@ -1,10 +1,12 @@
1
- require "bundler/gem_tasks"
2
- require "rake/testtask"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rake/testtask'
3
5
 
4
6
  Rake::TestTask.new(:test) do |t|
5
- t.libs << "test"
6
- t.libs << "lib"
7
- t.test_files = FileList["test/**/*_test.rb"]
7
+ t.libs << 'test'
8
+ t.libs << 'lib'
9
+ t.test_files = FileList['test/**/*_test.rb']
8
10
  end
9
11
 
10
- task :default => :test
12
+ task default: :test
data/lib/fulfil/client.rb CHANGED
@@ -5,8 +5,8 @@ require 'logger'
5
5
  require 'fulfil/response_parser'
6
6
 
7
7
  module Fulfil
8
- SUBDOMAIN = ENV['FULFIL_SUBDOMAIN']
9
- API_KEY = ENV['FULFIL_API_KEY']
8
+ SUBDOMAIN = ENV.fetch('FULFIL_SUBDOMAIN', nil)
9
+ API_KEY = ENV.fetch('FULFIL_API_KEY', nil)
10
10
 
11
11
  class Client
12
12
  class InvalidClientError < StandardError
@@ -93,6 +93,7 @@ module Fulfil
93
93
  uri = URI(model_url(model: model, id: id, endpoint: endpoint))
94
94
 
95
95
  result = request(verb: :put, endpoint: uri, json: body)
96
+
96
97
  parse(result: result)
97
98
  end
98
99
 
@@ -114,10 +115,10 @@ module Fulfil
114
115
  def oauth_token
115
116
  if ENV['FULFIL_TOKEN']
116
117
  puts "You're using an deprecated environment variable. Please update your " \
117
- 'FULFIL_TOKEN to FULFIL_OAUTH_TOKEN.'
118
+ 'FULFIL_TOKEN to FULFIL_OAUTH_TOKEN.'
118
119
  end
119
120
 
120
- ENV['FULFIL_OAUTH_TOKEN'] || ENV['FULFIL_TOKEN']
121
+ ENV['FULFIL_OAUTH_TOKEN'] || ENV.fetch('FULFIL_TOKEN', nil)
121
122
  end
122
123
 
123
124
  def parse(result: nil, results: [])
@@ -174,10 +175,9 @@ module Fulfil
174
175
  end
175
176
 
176
177
  def client
177
- client = HTTP.use(logging: @debug ? { logger: Logger.new(STDOUT) } : {})
178
+ client = HTTP.use(logging: @debug ? { logger: Logger.new($stdout) } : {})
178
179
  client = client.auth("Bearer #{@token}") if @token
179
- client = client.headers(@headers)
180
- client
180
+ client.headers(@headers)
181
181
  end
182
182
 
183
183
  def config
@@ -9,6 +9,19 @@ module Fulfil
9
9
  attr_accessor :retry_on_rate_limit
10
10
  attr_accessor :retry_on_rate_limit_wait
11
11
 
12
+ # Allows the client to configure a notification handler. Can be used by APM
13
+ # tools to monitor the number of rate limit hits.
14
+ #
15
+ # @example Use APM to monitor the API rate limit hits
16
+ # Fulfil.configure do |config|
17
+ # config.rate_limit_notification_handler = proc {
18
+ # FakeAPM.increment_counter('fulfil.rate_limit_exceeded')
19
+ # }
20
+ # end
21
+ #
22
+ # @return [Proc, nil]
23
+ attr_accessor :rate_limit_notification_handler
24
+
12
25
  def initialize
13
26
  @retry_on_rate_limit = false
14
27
  @retry_on_rate_limit_wait = 1
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Fulfil
2
4
  class InteractiveReport
3
5
  def initialize(client:, report:)
@@ -37,4 +39,4 @@ module Fulfil
37
39
  }
38
40
  end
39
41
  end
40
- end
42
+ end
data/lib/fulfil/model.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'fulfil/query'
2
4
 
3
5
  module Fulfil
@@ -12,15 +14,15 @@ module Fulfil
12
14
 
13
15
  # Delegate this to the client, including the model_name so we don't have to
14
16
  # type it every time.
15
- def find(model: model_name, id:)
17
+ def find(id:, model: model_name)
16
18
  @client.find(model: model, id: id)
17
19
  end
18
20
 
19
21
  # Delegate this to the client, including the model_name so we don't have to
20
22
  # type it every time.
21
23
  def search(
22
- model: model_name,
23
24
  domain:,
25
+ model: model_name,
24
26
  fields: %w[id rec_name],
25
27
  limit: nil,
26
28
  offset: nil,
@@ -51,7 +53,7 @@ module Fulfil
51
53
 
52
54
  def attributes
53
55
  results = @client.search(model: model_name, domain: [], limit: 1)
54
- @client.find(model: model_name, id: results.first.dig('id'))
56
+ @client.find(model: model_name, id: results.first['id'])
55
57
  end
56
58
 
57
59
  def fetch_associated(models:, association_name:, source_key:, fields:)
@@ -66,7 +68,7 @@ module Fulfil
66
68
  model: association_name, ids: associated_ids, fields: fields
67
69
  )
68
70
 
69
- associated_models_by_id = associated_models.map { |m| [m['id'], m] }.to_h
71
+ associated_models_by_id = associated_models.to_h { |m| [m['id'], m] }
70
72
 
71
73
  models.each do |model|
72
74
  filtered_models =
data/lib/fulfil/query.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Fulfil
2
4
  class Query
3
5
  def initialize
@@ -9,11 +11,12 @@ module Fulfil
9
11
  end
10
12
 
11
13
  def search(*args)
12
- options = args.first { |arg| arg.is_a?(Hash) && arg.keys.include?(:options) }.fetch(:options, {})
14
+ options = args.first { |arg| arg.is_a?(Hash) && arg.key?(:options) }.fetch(:options, {})
13
15
 
14
16
  args.each do |arg|
15
17
  arg.each do |field, value|
16
18
  next if value == options
19
+
17
20
  @matchers.concat(build_search_term(field: field, value: value, options: options))
18
21
  end
19
22
  end
@@ -22,17 +25,18 @@ module Fulfil
22
25
  end
23
26
 
24
27
  def exclude(*args)
25
- options = args.first { |arg| arg.is_a?(Hash) && arg.keys.include?(:options) }.fetch(:options, {})
28
+ options = args.first { |arg| arg.is_a?(Hash) && arg.key?(:options) }.fetch(:options, {})
26
29
 
27
30
  terms = args.flat_map do |arg|
28
- arg.map do |field, value|
29
- next if value == options
30
- build_exclude_term(field: field, value: value, options: options)
31
- end
32
- end
31
+ arg.map do |field, value|
32
+ next if value == options
33
+
34
+ build_exclude_term(field: field, value: value, options: options)
35
+ end
36
+ end
33
37
 
34
38
  if terms.length > 1
35
- @matchers.push(["OR"].concat(terms))
39
+ @matchers.push(['OR'].concat(terms))
36
40
  else
37
41
  @matchers.concat(terms.first)
38
42
  end
@@ -62,7 +66,7 @@ module Fulfil
62
66
  #
63
67
  # IN, NOT IN: (Array)
64
68
  #
65
- def build_search_term(prefix: nil, field:, value:, options:)
69
+ def build_search_term(field:, value:, options:, prefix: nil)
66
70
  key = [prefix, field.to_s].compact.join('.')
67
71
 
68
72
  case value.class.name
@@ -73,7 +77,7 @@ module Fulfil
73
77
  when 'Range'
74
78
  [
75
79
  [key, '>=', value.first],
76
- [key, '<=', value.last],
80
+ [key, '<=', value.last]
77
81
  ]
78
82
  when 'String'
79
83
  if options[:case_sensitive]
@@ -82,15 +86,15 @@ module Fulfil
82
86
  [[key, 'ilike', value]]
83
87
  end
84
88
  when 'Hash'
85
- value.flat_map { |nested_field, nested_value|
89
+ value.flat_map do |nested_field, nested_value|
86
90
  build_search_term(prefix: field, field: nested_field, value: nested_value, options: options)
87
- }
91
+ end
88
92
  else
89
93
  raise "Unhandled value type: #{value} (#{value.class.name})"
90
94
  end
91
95
  end
92
96
 
93
- def build_exclude_term(prefix: nil, field:, value:, options:)
97
+ def build_exclude_term(field:, value:, options:, prefix: nil)
94
98
  key = [prefix, field.to_s].compact.join('.')
95
99
 
96
100
  case value.class.name
@@ -101,12 +105,12 @@ module Fulfil
101
105
  when 'Range'
102
106
  [
103
107
  [key, '<', value.first],
104
- [key, '>', value.last],
108
+ [key, '>', value.last]
105
109
  ]
106
110
  when 'Hash'
107
- value.flat_map { |nested_field, nested_value|
111
+ value.flat_map do |nested_field, nested_value|
108
112
  build_exclude_term(prefix: field, field: nested_field, value: nested_value, options: options)
109
- }
113
+ end
110
114
  else
111
115
  raise "Unhandled value type: #{value} (#{value.class.name})"
112
116
  end
@@ -8,7 +8,8 @@ module Fulfil
8
8
 
9
9
  # Analyses the rate limit based on the response headers from Fulfil.
10
10
  # @param headers [HTTP::Headers] The HTTP response headers from Fulfil.
11
- # @return [Fulfil::RateLimit]
11
+ # @raise [Fulfil::RateLimitExceeded] When the rate limit is hit.
12
+ # @return [true]
12
13
  def analyse!(headers)
13
14
  rate_limit_headers = RateLimitHeaders.new(headers)
14
15
 
@@ -16,7 +17,9 @@ module Fulfil
16
17
  self.requests_left = rate_limit_headers.requests_left
17
18
  self.resets_at = rate_limit_headers.resets_at
18
19
 
19
- raise Fulfil::RateLimitExceeded unless requests_left?
20
+ return true if requests_left?
21
+
22
+ report_rate_limit_hit_and_raise
20
23
  end
21
24
 
22
25
  # Returns whether there are any requests left in the current rate limit window.
@@ -24,5 +27,13 @@ module Fulfil
24
27
  def requests_left?
25
28
  requests_left&.positive?
26
29
  end
30
+
31
+ private
32
+
33
+ # @raise [Fulfil::RateLimitExceeded]
34
+ def report_rate_limit_hit_and_raise
35
+ Fulfil.config.rate_limit_notification_handler&.call
36
+ raise Fulfil::RateLimitExceeded
37
+ end
27
38
  end
28
39
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Fulfil
2
4
  module ResponseParser
3
5
  class UnhandledTypeError < StandardError
@@ -17,19 +19,19 @@ module Fulfil
17
19
  # }
18
20
  #
19
21
  def self.mapped_value_field(value:)
20
- return value unless value.is_a?(Hash) && value.dig('__class__')
22
+ return value unless value.is_a?(Hash) && value['__class__']
21
23
 
22
- json_class = value.dig('__class__')
24
+ json_class = value['__class__']
23
25
 
24
26
  case json_class
25
27
  when 'date'
26
- date = value.dig('iso_string')
28
+ date = value['iso_string']
27
29
  Date.parse(date)
28
30
  when 'datetime'
29
- time = value.dig('iso_string')
31
+ time = value['iso_string']
30
32
  DateTime.parse(time)
31
33
  when 'Decimal', 'timedelta'
32
- value.dig('decimal').to_f
34
+ value['decimal'].to_f
33
35
  else
34
36
  raise UnhandledTypeError.new(
35
37
  "received a value that we don't know how to handle: #{json_class}",
@@ -46,15 +48,19 @@ module Fulfil
46
48
  [group_key, mapped_value_field(value: kv_tuples[0][1])]
47
49
  else
48
50
  id = kv_tuples[0]
49
- attrs = kv_tuples[1..-1].map { |tuple| [tuple[0][1..-1], tuple[1]] }
51
+ attrs = kv_tuples[1..].map { |tuple| [tuple[0][1..], tuple[1]] }
50
52
  [group_key, [['id', id[1]]].concat(group(attrs)).to_h]
51
53
  end
52
54
  end
53
55
  end
54
56
 
55
57
  def self.parse(item:)
56
- key_value_tuples = item.to_a.map { |item_tuple| [item_tuple[0].split('.'), item_tuple[1]] }
57
- group(key_value_tuples).to_h
58
+ case item
59
+ when Hash
60
+ group(item.map { |(key, value)| [key.split('.'), value] }).to_h
61
+ else
62
+ item
63
+ end
58
64
  end
59
65
  end
60
66
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fulfil
4
- VERSION = "0.7.0"
4
+ VERSION = '0.8.0'
5
5
  end
metadata CHANGED
@@ -1,15 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fulfil-io
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Moore
8
8
  - Kat Fairbanks
9
+ - Stefan Vermaas
9
10
  autorequire:
10
11
  bindir: bin
11
12
  cert_chain: []
12
- date: 2022-09-22 00:00:00.000000000 Z
13
+ date: 2023-04-20 00:00:00.000000000 Z
13
14
  dependencies:
14
15
  - !ruby/object:Gem::Dependency
15
16
  name: http
@@ -20,7 +21,7 @@ dependencies:
20
21
  version: 4.4.1
21
22
  - - "<"
22
23
  - !ruby/object:Gem::Version
23
- version: 5.1.0
24
+ version: 5.2.0
24
25
  type: :runtime
25
26
  prerelease: false
26
27
  version_requirements: !ruby/object:Gem::Requirement
@@ -30,111 +31,7 @@ dependencies:
30
31
  version: 4.4.1
31
32
  - - "<"
32
33
  - !ruby/object:Gem::Version
33
- version: 5.1.0
34
- - !ruby/object:Gem::Dependency
35
- name: bundler
36
- requirement: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '2.0'
41
- type: :development
42
- prerelease: false
43
- version_requirements: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '2.0'
48
- - !ruby/object:Gem::Dependency
49
- name: minitest
50
- requirement: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '5.0'
55
- type: :development
56
- prerelease: false
57
- version_requirements: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '5.0'
62
- - !ruby/object:Gem::Dependency
63
- name: minitest-reporters
64
- requirement: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '1.3'
69
- type: :development
70
- prerelease: false
71
- version_requirements: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: '1.3'
76
- - !ruby/object:Gem::Dependency
77
- name: oauth2
78
- requirement: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: '1.4'
83
- type: :development
84
- prerelease: false
85
- version_requirements: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '1.4'
90
- - !ruby/object:Gem::Dependency
91
- name: rake
92
- requirement: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ">="
95
- - !ruby/object:Gem::Version
96
- version: '0'
97
- type: :development
98
- prerelease: false
99
- version_requirements: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - ">="
102
- - !ruby/object:Gem::Version
103
- version: '0'
104
- - !ruby/object:Gem::Dependency
105
- name: webmock
106
- requirement: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - ">="
109
- - !ruby/object:Gem::Version
110
- version: '0'
111
- type: :development
112
- prerelease: false
113
- version_requirements: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - ">="
116
- - !ruby/object:Gem::Version
117
- version: '0'
118
- - !ruby/object:Gem::Dependency
119
- name: dotenv
120
- requirement: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - "~>"
123
- - !ruby/object:Gem::Version
124
- version: '2.7'
125
- - - ">="
126
- - !ruby/object:Gem::Version
127
- version: 2.7.6
128
- type: :development
129
- prerelease: false
130
- version_requirements: !ruby/object:Gem::Requirement
131
- requirements:
132
- - - "~>"
133
- - !ruby/object:Gem::Version
134
- version: '2.7'
135
- - - ">="
136
- - !ruby/object:Gem::Version
137
- version: 2.7.6
34
+ version: 5.2.0
138
35
  description:
139
36
  email:
140
37
  - chris@knowndecimal.com
@@ -165,7 +62,8 @@ files:
165
62
  homepage: https://github.com/knowndecimal/fulfil
166
63
  licenses:
167
64
  - MIT
168
- metadata: {}
65
+ metadata:
66
+ rubygems_mfa_required: 'true'
169
67
  post_install_message:
170
68
  rdoc_options: []
171
69
  require_paths:
@@ -174,7 +72,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
174
72
  requirements:
175
73
  - - ">="
176
74
  - !ruby/object:Gem::Version
177
- version: '2.4'
75
+ version: '2.6'
178
76
  required_rubygems_version: !ruby/object:Gem::Requirement
179
77
  requirements:
180
78
  - - ">="