X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/34f7549bb4dba0f35290b473fa7b1d3d6654b90c..5f5bb630ce1830f1665edbe3c90db2254debd70c:/sdk/ruby/lib/arvados.rb diff --git a/sdk/ruby/lib/arvados.rb b/sdk/ruby/lib/arvados.rb index 51a1d403a6..f4941238c0 100644 --- a/sdk/ruby/lib/arvados.rb +++ b/sdk/ruby/lib/arvados.rb @@ -2,9 +2,11 @@ require 'rubygems' require 'google/api_client' require 'active_support/inflector' require 'json' +require 'fileutils' ActiveSupport::Inflector.inflections do |inflect| inflect.irregular 'specimen', 'specimens' + inflect.irregular 'human', 'humans' end module Kernel @@ -19,6 +21,10 @@ end class Arvados + class TransactionFailedError < StandardError + end + + @@config = nil @@debuglevel = 0 class << self attr_accessor :debuglevel @@ -29,18 +35,17 @@ class Arvados @application_name ||= File.split($0).last @arvados_api_version = opts[:api_version] || - ENV['ARVADOS_API_VERSION'] || + config['ARVADOS_API_VERSION'] || 'v1' @arvados_api_host = opts[:api_host] || - ENV['ARVADOS_API_HOST'] or + config['ARVADOS_API_HOST'] or raise "#{$0}: no :api_host or ENV[ARVADOS_API_HOST] provided." @arvados_api_token = opts[:api_token] || - ENV['ARVADOS_API_TOKEN'] or + config['ARVADOS_API_TOKEN'] or raise "#{$0}: no :api_token or ENV[ARVADOS_API_TOKEN] provided." - @suppress_ssl_warnings = opts[:suppress_ssl_warnings] || false - - if @suppress_ssl_warnings + if (opts[:suppress_ssl_warnings] or + config['ARVADOS_API_HOST_INSECURE']) suppress_warnings do OpenSSL::SSL.const_set 'VERIFY_PEER', OpenSSL::SSL::VERIFY_NONE end @@ -50,6 +55,7 @@ class Arvados # resource. After this, self.job will return Arvados::Job; # self.job.new() and self.job.find() will do what you want. _arvados = self + namespace_class = Arvados.const_set "A#{self.object_id}", Class.new self.arvados_api.schemas.each do |classname, schema| next if classname.match /List$/ klass = Class.new(Arvados::Model) do @@ -69,11 +75,10 @@ class Arvados arvados_api. send(classname.underscore.split('/').last.pluralize.to_sym). discovered_methods. - collect(&:name). - each do |method_name| + each do |method| class << klass; self; end.class_eval do - define_method method_name do |*params| - self.api_exec(method_name.to_sym, *params) + define_method method.name do |*params| + self.api_exec(method.name.to_sym, *params) end end end @@ -86,9 +91,10 @@ class Arvados @api_model_sym = classname.underscore.split('/').last.to_sym end - # This might produce confusing results when using multiple - # Arvados instances. - Arvados.const_set classname, klass + # Create the new class in namespace_class so it doesn't + # interfere with classes created by other Arvados objects. The + # result looks like Arvados::A26949680::Job. + namespace_class.const_set classname, klass self.class.class_eval do define_method classname.underscore do @@ -103,12 +109,19 @@ class Arvados api = api.to_s return @discovery_documents["#{api}:#{version}"] ||= begin - response = self.execute!( - :http_method => :get, - :uri => self.discovery_uri(api, version), - :authenticated => false - ) - response.body.class == String ? JSON.parse(response.body) : response.body + # fetch new API discovery doc if stale + cached_doc = File.expand_path '~/.cache/arvados/discovery_uri.json' + if not File.exist?(cached_doc) or (Time.now - File.mtime(cached_doc)) > 86400 + response = self.execute!(:http_method => :get, + :uri => self.discovery_uri(api, version), + :authenticated => false) + FileUtils.makedirs(File.dirname cached_doc) + File.open(cached_doc, 'w') do |f| + f.puts response.body + end + end + + File.open(cached_doc) { |f| JSON.load f } end end end @@ -128,6 +141,37 @@ class Arvados $stderr.puts "#{File.split($0).last} #{$$}: #{message}" if @@debuglevel >= verbosity end + def config(config_file_path="~/.config/arvados/settings.conf") + return @@config if @@config + + # Initialize config settings with environment variables. + config = {} + config['ARVADOS_API_HOST'] = ENV['ARVADOS_API_HOST'] + config['ARVADOS_API_TOKEN'] = ENV['ARVADOS_API_TOKEN'] + config['ARVADOS_API_HOST_INSECURE'] = ENV['ARVADOS_API_HOST_INSECURE'] + config['ARVADOS_API_VERSION'] = ENV['ARVADOS_API_VERSION'] + + expanded_path = File.expand_path config_file_path + if File.exist? expanded_path + # Load settings from the config file. + lineno = 0 + File.open(expanded_path).each do |line| + lineno = lineno + 1 + # skip comments and blank lines + next if line.match('^\s*#') or not line.match('\S') + var, val = line.chomp.split('=', 2) + # allow environment settings to override config files. + if var and val + config[var] ||= val + else + warn "#{expanded_path}: #{lineno}: could not parse `#{line}'" + end + end + end + + @@config = config + end + class Model def self.arvados_api arvados.arvados_api @@ -143,7 +187,7 @@ class Arvados end def self.api_exec(method, parameters={}) parameters = parameters. - merge(:api_token => ENV['ARVADOS_API_TOKEN']) + merge(:api_token => arvados.config['ARVADOS_API_TOKEN']) parameters.each do |k,v| parameters[k] = v.to_json if v.is_a? Array or v.is_a? Hash end @@ -151,7 +195,18 @@ class Arvados execute(:api_method => arvados_api.send(api_models_sym).send(method), :authenticated => false, :parameters => parameters) - JSON.parse result.body, :symbolize_names => true + resp = JSON.parse result.body, :symbolize_names => true + if resp[:errors] + raise Arvados::TransactionFailedError.new(resp[:errors]) + elsif resp[:uuid] and resp[:etag] + self.new(resp) + elsif resp[:items].is_a? Array + resp.merge(items: resp[:items].collect do |i| + self.new(i) + end) + else + resp + end end def []=(x,y) @@ -175,7 +230,7 @@ class Arvados :uuid => @attributes[:uuid], self.class.api_model_sym => @attributes_to_update.to_json } - unless j.is_a? Hash and j[:uuid] + unless j.respond_to? :[] and j[:uuid] debuglog "Failed to save #{self.to_s}: #{j[:errors] rescue nil}", 0 nil else