roqua/opencpu

View on GitHub
lib/opencpu/client.rb

Summary

Maintainability
A
1 hr
Test Coverage
require 'opencpu/errors'

module OpenCPU
  class Client
    include Errors
    include HTTParty

    def initialize
      self.class.base_uri OpenCPU.configuration.endpoint_url
      self.class.default_timeout(OpenCPU.configuration.timeout) unless OpenCPU.configuration.timeout.nil?
    end

    def execute(package, function, options = {})
      user                      = options.fetch :user, :system
      data                      = options.fetch :data, {}
      format                    = options.fetch :format, :json
      github_remote             = options.fetch :github_remote, false
      should_convert_na_to_nil  = options.fetch :convert_na_to_nil, false

      process_query function_url(package, function, user, github_remote, :json), data, format do |response|
        output = JSON.parse(response.body)
        output = convert_na_to_nil(output) if should_convert_na_to_nil
        output
      end
    end

    def description(package, options = {})
      user          = options.fetch :user, :system
      github_remote = options.fetch :github_remote, false

      url = "#{package_url(package, user, github_remote)}/info"
      self.class.get(url, request_options(nil, :json))
    end

    def convert_na_to_nil(data)
      case data
      when 'NA'
        nil
      when Hash
        data.each { |k, v| data[k] = convert_na_to_nil(v) }
      when Array
        data.map! { |v| convert_na_to_nil(v) }
      else
        data
      end
    end

    def prepare(package, function, options = {})
      user = options.fetch :user, :system
      data = options.fetch :data, {}
      format = options.fetch :format, :json
      github_remote = options.fetch :github_remote, false
      process_query function_url(package, function, user, github_remote), data, format do |response|
        location  = response.headers['location']
        resources = response.body.split(/\n/)
        OpenCPU::DelayedCalculation.new(location, resources)
      end
    end

    private

    def process_query(url, data, format, &block)
      return fake_response_for(url) if OpenCPU.test_mode?

      response  = self.class.post(url, request_options(data, format))

      case response.code
      when 200..201
        return yield(response)
      else
        fail error_class_for(response.code), "#{response.code}:\n #{response.body}"
      end
    end

    def error_class_for(response_code)
      case response_code
      when 403
        AccessDenied
      when 400..499
        BadRequest
      when 500..599
        InternalServerError
      else
        OpenCPUError
      end
    end

    def request_options(data, format)
      options = {
        verify: OpenCPU.configuration.verify_ssl
      }

      case format
      when :json
        options[:body] = data.to_json if data
        options[:headers] =  {"Content-Type" => 'application/json'}
      else # :urlencoded / :multipart if a file is present.
        options[:body] = data if data
      end

      if OpenCPU.configuration.username && OpenCPU.configuration.password
        options[:basic_auth] = {
          username: OpenCPU.configuration.username, password: OpenCPU.configuration.password
        }
      end
      options
    end

    def function_url(package, function, user = :system, github_remote = false, format = nil)
      "#{package_url(package, user, github_remote)}/R/#{function}/#{format.to_s}"
    end

    def package_url(package, user = :system, github_remote = false)
      return "/library/#{package}" if user == :system
      return "/github/#{user}/#{package}" if github_remote
      return "/user/#{user}/library/#{package}"
    end

    def fake_response_for(url)
      key = derive_key_from_url(url)
      OpenCPU.configuration.fake_responses.delete key
    end

    def derive_key_from_url(url)
      url_parts    = url.gsub!(/^\//, '').split('/')
      remove_items = ['R', 'library', 'json']
      (url_parts - remove_items).join('/')
    end
  end
end