easyship 0.2.0 → 0.3.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: 7c050cd7909443866f9f0365555f577024aa49ce0065be2a961018385409972d
4
- data.tar.gz: a827b2cdaa8207b8723f5a7c2b65efba7c6b7f9e10cf16b46ea36f34aebae763
3
+ metadata.gz: 0e185adc98f576e3cf15ffc1ca38edf2dca8a0ffa580f68a731c900c3632392b
4
+ data.tar.gz: e15d52b3f530c58883fb8f50b086db537939ceed2fc1afff3579ea6aa82373c1
5
5
  SHA512:
6
- metadata.gz: 959f2149e8e0d3f33ce0592de28df9718a0fb1c24cdb78909acffbdcecbeab71449b29a1000c03589d24616603214f0f9aa0dc05bad67e96ee413b2acad2a25c
7
- data.tar.gz: 0ba62b8bf714db84417d4c2abb13e66738b0510dd0f57997c9403cdbfb81e2fa78029fb9b058d07a5ed943bb5eb8a9054be8929f6a7975b816fc59dda360b781
6
+ metadata.gz: 707860b193dbc25e916f8d0e5e68a2a3c8e34498e523871e243cf487f1e5c4c2469ed274099899b1b50c22598dc085906eca132f9e537515c43bf03428040c02
7
+ data.tar.gz: ec38670ff9d6626bc62485d8c836d00c1efb8e4a8218214c5c6b36b01570ad3b71906511c2459dc4a74b3a5ed91e3d3f91bd5f07586e75d481dc2ebf142a4d9a
data/CHANGELOG.md CHANGED
@@ -1,6 +1,12 @@
1
1
  ## [Unreleased]
2
2
 
3
- ## [v.0.2.0](https://github.com/mmarusyk/easyship/tree/v0.2.0) - 2025-05-04
3
+ ## [v0.3.0](https://github.com/mmarusyk/easyship/tree/v0.3.0) - 2025-11-29
4
+
5
+ ### Added
6
+ - Rate limiting for cursor by @mmarusyk in https://github.com/mmarusyk/easyship/pull/13
7
+
8
+
9
+ ## [v0.2.0](https://github.com/mmarusyk/easyship/tree/v0.2.0) - 2025-05-04
4
10
 
5
11
  ### Added
6
12
  - Add response_body and response_header to Easyship::Error
data/README.md CHANGED
@@ -58,7 +58,7 @@ Easyship.configure do |config|
58
58
  end
59
59
  ```
60
60
 
61
- Configuration supports the next keys: `url`, `api_key`, `per_page`.
61
+ Configuration supports the next keys: `url`, `api_key`, `per_page`, `requests_per_second`, `requests_per_minute`.
62
62
 
63
63
  ### Making Requests
64
64
  `Easyship::Client` supports the next methods: `get`, `post`, `put`, `delete`.
@@ -131,7 +131,48 @@ end
131
131
  shipments # Returns all shipments from all pages
132
132
  ```
133
133
 
134
- To setup items perpage, use the key `per_page` in your configuration.
134
+ To setup items per page, use the key `per_page` in your configuration.
135
+
136
+ For Example:
137
+
138
+ ```ruby
139
+ # Global defaults
140
+ Easyship.configure do |config|
141
+ config.per_page = 100
142
+ end
143
+
144
+ # Per-call overrides (any of these are supported)
145
+ shipments = []
146
+ Easyship::Client.instance.get('/2023-01/shipments', {
147
+ per_page: 50,
148
+ }) do |page|
149
+ shipments.concat(page[:shipments])
150
+ end
151
+ ```
152
+
153
+ Rate limiting during pagination:
154
+ - The cursor has no default rate limiting; it uses nil to indicate that rate limiting is disabled.
155
+ - Use [Rate limiting documentation](https://developers.easyship.com/reference/rate-limit) for more details which values to set.
156
+ - You can override the limits per call, by passing `requests_per_second` and `requests_per_minute`.
157
+
158
+ Examples:
159
+
160
+ ```ruby
161
+ # Global defaults
162
+ Easyship.configure do |config|
163
+ config.requests_per_second = 10
164
+ config.requests_per_minute = 60
165
+ end
166
+
167
+ # Per-call overrides (any of these are supported)
168
+ shipments = []
169
+ Easyship::Client.instance.get('/2023-01/shipments', {
170
+ requests_per_second: 5,
171
+ requests_per_minute: 40,
172
+ }) do |page|
173
+ shipments.concat(page[:shipments])
174
+ end
175
+ ```
135
176
 
136
177
  ## Development
137
178
 
@@ -3,12 +3,14 @@
3
3
  module Easyship
4
4
  # Represents the configuration settings for the Easyship client.
5
5
  class Configuration
6
- attr_accessor :url, :api_key, :per_page
6
+ attr_accessor :url, :api_key, :per_page, :requests_per_second, :requests_per_minute
7
7
 
8
8
  def initialize
9
9
  @url = nil
10
10
  @api_key = nil
11
11
  @per_page = 100 # Maximum possible number of items per page
12
+ @requests_per_second = nil
13
+ @requests_per_minute = nil
12
14
  end
13
15
  end
14
16
  end
@@ -4,20 +4,26 @@ module Easyship
4
4
  module Pagination
5
5
  # Represents a pagination object
6
6
  class Cursor
7
- attr_reader :client, :path, :params, :key, :per_page
7
+ CONFIGURATION_VARIABLES = %i[requests_per_second requests_per_minute].freeze
8
+
9
+ attr_reader :client, :path, :params, :key, :per_page, :requests_per_second, :requests_per_minute
8
10
 
9
11
  def initialize(client, path, params)
10
12
  @client = client
11
13
  @path = path
12
14
  @params = params
13
15
  @per_page = params[:per_page] || Easyship.configuration.per_page
16
+ @requests_per_second = params[:requests_per_second] || Easyship.configuration.requests_per_second
17
+ @requests_per_minute = params[:requests_per_minute] || Easyship.configuration.requests_per_minute
14
18
  end
15
19
 
16
20
  def all
17
21
  page = 1
18
22
 
19
23
  loop do
20
- body = client.get(path, params.merge(page: page, per_page: per_page))
24
+ limiter.throttle!
25
+
26
+ body = client.get(path, build_request_params(page: page))
21
27
 
22
28
  break if body.nil? || body.empty?
23
29
 
@@ -28,6 +34,19 @@ module Easyship
28
34
  page += 1
29
35
  end
30
36
  end
37
+
38
+ private
39
+
40
+ def build_request_params(page:)
41
+ params.merge(page: page, per_page: per_page).except(*CONFIGURATION_VARIABLES)
42
+ end
43
+
44
+ def limiter
45
+ @limiter ||= Easyship::RateLimiting::WindowRateLimiter.new(
46
+ requests_per_second: requests_per_second,
47
+ requests_per_minute: requests_per_minute
48
+ )
49
+ end
31
50
  end
32
51
  end
33
52
  end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Easyship
4
+ module RateLimiting
5
+ # Represents RateLimiter
6
+ class RateLimiter
7
+ def throttle!
8
+ raise NotImplementedError
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Easyship
4
+ module RateLimiting
5
+ # Represents WindowRateLimiter
6
+ class WindowRateLimiter < RateLimiter
7
+ attr_reader :timestamps, :requests_per_second, :requests_per_minute
8
+
9
+ def initialize(requests_per_second:, requests_per_minute:)
10
+ super()
11
+ @requests_per_second = requests_per_second
12
+ @requests_per_minute = requests_per_minute
13
+ @timestamps = []
14
+ end
15
+
16
+ def throttle!
17
+ now = Time.now
18
+
19
+ timestamps << now
20
+
21
+ # Remove timestamps older than a minute
22
+ timestamps.reject! { |t| t < now - 60 }
23
+
24
+ check_second_window(now)
25
+ check_minute_window(now)
26
+ end
27
+
28
+ private
29
+
30
+ def check_second_window(now)
31
+ second_requests = timestamps.count { |t| t > now - 1 }
32
+
33
+ return if !requests_per_second || second_requests < requests_per_second
34
+
35
+ first_in_seconds = timestamps.find { |t| t > now - 1 }
36
+ sleep_time = (first_in_seconds + 1) - now
37
+
38
+ sleep(sleep_time) if sleep_time.positive?
39
+ end
40
+
41
+ def check_minute_window(now)
42
+ return if !requests_per_minute || timestamps.size < requests_per_minute
43
+
44
+ sleep_time = (timestamps.first + 60) - now
45
+
46
+ sleep(sleep_time) if sleep_time.positive?
47
+ end
48
+ end
49
+ end
50
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Easyship
4
- VERSION = '0.2.0'
4
+ VERSION = '0.3.0'
5
5
  end
data/lib/easyship.rb CHANGED
@@ -3,6 +3,8 @@
3
3
  require_relative 'easyship/version'
4
4
  require_relative 'easyship/configuration'
5
5
  require_relative 'easyship/client'
6
+ require_relative 'easyship/rate_limiting/rate_limiter'
7
+ require_relative 'easyship/rate_limiting/window_rate_limiter'
6
8
 
7
9
  # Provides configuration options for the Easyship gem.
8
10
  module Easyship
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: easyship
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Marusyk
@@ -56,6 +56,8 @@ files:
56
56
  - lib/easyship/handlers/response_body_handler.rb
57
57
  - lib/easyship/middleware/error_handler_middleware.rb
58
58
  - lib/easyship/pagination/cursor.rb
59
+ - lib/easyship/rate_limiting/rate_limiter.rb
60
+ - lib/easyship/rate_limiting/window_rate_limiter.rb
59
61
  - lib/easyship/version.rb
60
62
  - sig/easyship.rbs
61
63
  homepage: https://rubygems.org/gems/easyship
@@ -81,7 +83,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
81
83
  - !ruby/object:Gem::Version
82
84
  version: '0'
83
85
  requirements: []
84
- rubygems_version: 3.6.7
86
+ rubygems_version: 3.6.9
85
87
  specification_version: 4
86
88
  summary: A Ruby client for integrating with Easyship's API for shipping and logistics
87
89
  management.