lucid_shopify 0.19.0 → 0.20.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/lucid_shopify.rb +6 -0
- data/lib/lucid_shopify/container.rb +7 -1
- data/lib/lucid_shopify/redis_throttled_strategy.rb +79 -0
- data/lib/lucid_shopify/send_request.rb +4 -3
- data/lib/lucid_shopify/throttled_strategy.rb +5 -4
- data/lib/lucid_shopify/version.rb +1 -1
- metadata +3 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 2949f5da34bd486fe5ac4a76104577c730c5c086e36bde82c2bccc2925df3ade
         | 
| 4 | 
            +
              data.tar.gz: c402dbe292c0b59a04e8b57d87b09017e666681e72dd99af180f7041a51c99d0
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 1dc26e15a811b752753df1aeda9b1763de6edb8eef58e8277c691c9cf428578b7d0dd3742f03ff64d26d31eb04fd15f428ecfdec49b02f36763a23c2769969e4
         | 
| 7 | 
            +
              data.tar.gz: efbdeeff6b99542cbe79ab72b0bcfdbd5b9f11c64ee19fc160fe80a5bc37c1aae6920b1b853463775a9b0d5b582fe9f5465c1ad1eb618b5b84935bec5df4888a
         | 
    
        data/lib/lucid_shopify.rb
    CHANGED
    
    | @@ -2,6 +2,11 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            require 'dry/initializer'
         | 
| 4 4 |  | 
| 5 | 
            +
            begin
         | 
| 6 | 
            +
              require 'redis'
         | 
| 7 | 
            +
            rescue LoadError
         | 
| 8 | 
            +
            end
         | 
| 9 | 
            +
             | 
| 5 10 | 
             
            module LucidShopify
         | 
| 6 11 | 
             
              autoload :ActivateCharge, 'lucid_shopify/activate_charge'
         | 
| 7 12 | 
             
              autoload :Authorize, 'lucid_shopify/authorize'
         | 
| @@ -18,6 +23,7 @@ module LucidShopify | |
| 18 23 | 
             
              autoload :GetRequest, 'lucid_shopify/get_request'
         | 
| 19 24 | 
             
              autoload :PostRequest, 'lucid_shopify/post_request'
         | 
| 20 25 | 
             
              autoload :PutRequest, 'lucid_shopify/put_request'
         | 
| 26 | 
            +
              autoload :RedisThrottledStrategy, 'lucid_shopify/redis_throttled_strategy'
         | 
| 21 27 | 
             
              autoload :Request, 'lucid_shopify/request'
         | 
| 22 28 | 
             
              autoload :Response, 'lucid_shopify/response'
         | 
| 23 29 | 
             
              autoload :Result, 'lucid_shopify/result'
         | 
| @@ -20,7 +20,13 @@ module LucidShopify | |
| 20 20 | 
             
              Container.register(:delete_webhook) { DeleteWebhook.new }
         | 
| 21 21 | 
             
              Container.register(:http) { ::HTTP::Client.new }
         | 
| 22 22 | 
             
              Container.register(:send_request) { SendRequest.new }
         | 
| 23 | 
            -
              Container.register(:send_throttled_request)  | 
| 23 | 
            +
              Container.register(:send_throttled_request) do
         | 
| 24 | 
            +
                if defined?(Redis)
         | 
| 25 | 
            +
                  SendRequest.new(strategy: RedisThrottledStrategy.new)
         | 
| 26 | 
            +
                else
         | 
| 27 | 
            +
                  SendRequest.new(strategy: ThrottledStrategy.new)
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
              end
         | 
| 24 30 | 
             
              Container.register(:verify_callback) { VerifyCallback.new }
         | 
| 25 31 | 
             
              Container.register(:verify_webhook) { VerifyWebhook.new }
         | 
| 26 32 | 
             
              Container.register(:webhook_handler_list) { LucidShopify.handlers }
         | 
| @@ -0,0 +1,79 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'lucid_shopify'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            if defined?(Redis)
         | 
| 6 | 
            +
              module LucidShopify
         | 
| 7 | 
            +
                #
         | 
| 8 | 
            +
                # Use Redis to maintain API call limit throttling across threads/processes.
         | 
| 9 | 
            +
                #
         | 
| 10 | 
            +
                # No delay for requests up to half of the call limit.
         | 
| 11 | 
            +
                #
         | 
| 12 | 
            +
                class RedisThrottledStrategy < ThrottledStrategy
         | 
| 13 | 
            +
                  LEAK_RATE = 500
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  #
         | 
| 16 | 
            +
                  # @param redis_client [Redis]
         | 
| 17 | 
            +
                  #
         | 
| 18 | 
            +
                  def initialize(redis_client: Redis.current)
         | 
| 19 | 
            +
                    @redis_client = redis_client
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  #
         | 
| 23 | 
            +
                  # @param request [Request]
         | 
| 24 | 
            +
                  #
         | 
| 25 | 
            +
                  # @yieldreturn [Response]
         | 
| 26 | 
            +
                  #
         | 
| 27 | 
            +
                  # @return [Response]
         | 
| 28 | 
            +
                  #
         | 
| 29 | 
            +
                  def call(request, &send_request)
         | 
| 30 | 
            +
                    interval_key = build_interval_key(request)
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    interval(interval_key)
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                    send_request.().tap do |res|
         | 
| 35 | 
            +
                      cur, max = res.headers['X-Shopify-Shop-Api-Call-Limit'].split('/')
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                      @redis_client.mapped_hmset(interval_key,
         | 
| 38 | 
            +
                        cur: cur,
         | 
| 39 | 
            +
                        max: max,
         | 
| 40 | 
            +
                        at: timestamp
         | 
| 41 | 
            +
                      )
         | 
| 42 | 
            +
                    end
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  #
         | 
| 46 | 
            +
                  # If over half the call limit, sleep until requests leak back to the
         | 
| 47 | 
            +
                  # threshold.
         | 
| 48 | 
            +
                  #
         | 
| 49 | 
            +
                  # @param interval_key [String]
         | 
| 50 | 
            +
                  #
         | 
| 51 | 
            +
                  private def interval(interval_key)
         | 
| 52 | 
            +
                    cur, max, at = @redis_client.hmget(interval_key, :cur, :max, :at).map(&:to_i)
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                    cur = leak(cur, at)
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                    delay_threshold = max / 2 # no delay
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                    if cur > delay_threshold
         | 
| 59 | 
            +
                      sleep(Rational((cur - delay_threshold) * LEAK_RATE, 1000))
         | 
| 60 | 
            +
                    end
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                  #
         | 
| 64 | 
            +
                  # Find the actual value of {cur}, by subtracting requests leaked by the
         | 
| 65 | 
            +
                  # leaky bucket algorithm since the value was set.
         | 
| 66 | 
            +
                  #
         | 
| 67 | 
            +
                  # @param cur [Integer]
         | 
| 68 | 
            +
                  # @param at [Integer]
         | 
| 69 | 
            +
                  #
         | 
| 70 | 
            +
                  # @return [Integer]
         | 
| 71 | 
            +
                  #
         | 
| 72 | 
            +
                  private def leak(cur, at)
         | 
| 73 | 
            +
                    n = Rational(timestamp - at, LEAK_RATE).floor
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                    n > cur ? 0 : cur - n
         | 
| 76 | 
            +
                  end
         | 
| 77 | 
            +
                end
         | 
| 78 | 
            +
              end
         | 
| 79 | 
            +
            end
         | 
| @@ -75,7 +75,7 @@ module LucidShopify | |
| 75 75 | 
             
                    req.object_id,
         | 
| 76 76 | 
             
                    req.http_method.to_s.upcase,
         | 
| 77 77 | 
             
                    req.url,
         | 
| 78 | 
            -
                    req.options[:params]&.to_json || '{}'
         | 
| 78 | 
            +
                    req.options[:params]&.to_json || '{}',
         | 
| 79 79 | 
             
                  ])
         | 
| 80 80 | 
             
                end
         | 
| 81 81 |  | 
| @@ -87,10 +87,11 @@ module LucidShopify | |
| 87 87 | 
             
                  req = request
         | 
| 88 88 | 
             
                  res = response
         | 
| 89 89 |  | 
| 90 | 
            -
                  LucidShopify.config.logger.info('<%s> [%i] %i' % [
         | 
| 90 | 
            +
                  LucidShopify.config.logger.info('<%s> [%i] %i (%s)' % [
         | 
| 91 91 | 
             
                    self.class.to_s,
         | 
| 92 92 | 
             
                    req.object_id,
         | 
| 93 | 
            -
                    res.status_code
         | 
| 93 | 
            +
                    res.status_code,
         | 
| 94 | 
            +
                    res.headers['X-Shopify-Shop-Api-Call-Limit'],
         | 
| 94 95 | 
             
                  ])
         | 
| 95 96 | 
             
                end
         | 
| 96 97 |  | 
| @@ -3,6 +3,9 @@ | |
| 3 3 | 
             
            require 'lucid_shopify'
         | 
| 4 4 |  | 
| 5 5 | 
             
            module LucidShopify
         | 
| 6 | 
            +
              #
         | 
| 7 | 
            +
              # Maintain API call limit throttling across a single thread.
         | 
| 8 | 
            +
              #
         | 
| 6 9 | 
             
              class ThrottledStrategy
         | 
| 7 10 | 
             
                MINIMUM_INTERVAL = 500 # ms
         | 
| 8 11 |  | 
| @@ -20,13 +23,11 @@ module LucidShopify | |
| 20 23 | 
             
                end
         | 
| 21 24 |  | 
| 22 25 | 
             
                #
         | 
| 23 | 
            -
                #  | 
| 24 | 
            -
                #  | 
| 26 | 
            +
                # If time since the last request < {MINIMUM_INTERVAL}, then sleep for the
         | 
| 27 | 
            +
                # difference.
         | 
| 25 28 | 
             
                #
         | 
| 26 29 | 
             
                # @param interval_key [String]
         | 
| 27 30 | 
             
                #
         | 
| 28 | 
            -
                # @note Throttling is only maintained across a single thread.
         | 
| 29 | 
            -
                #
         | 
| 30 31 | 
             
                private def interval(interval_key)
         | 
| 31 32 | 
             
                  if Thread.current[interval_key]
         | 
| 32 33 | 
             
                    (timestamp - Thread.current[interval_key]).tap do |n|
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: lucid_shopify
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.20.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Kelsey Judson
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2018-12- | 
| 11 | 
            +
            date: 2018-12-02 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: dotenv
         | 
| @@ -132,6 +132,7 @@ files: | |
| 132 132 | 
             
            - lib/lucid_shopify/get_request.rb
         | 
| 133 133 | 
             
            - lib/lucid_shopify/post_request.rb
         | 
| 134 134 | 
             
            - lib/lucid_shopify/put_request.rb
         | 
| 135 | 
            +
            - lib/lucid_shopify/redis_throttled_strategy.rb
         | 
| 135 136 | 
             
            - lib/lucid_shopify/request.rb
         | 
| 136 137 | 
             
            - lib/lucid_shopify/response.rb
         | 
| 137 138 | 
             
            - lib/lucid_shopify/send_request.rb
         |