From bf6834445feb6f93f0a20359462dbd1e7e89f4b8 Mon Sep 17 00:00:00 2001 From: Christoph Helma Date: Thu, 12 Jul 2012 16:38:03 +0200 Subject: all opentox-client tests pass --- lib/compound.rb | 12 ++--- lib/dataset.rb | 134 +++++++++++++++++++++++++++++++++++++++++++------- lib/error.rb | 5 +- lib/opentox-client.rb | 2 + lib/opentox.rb | 80 +++++++++++++----------------- 5 files changed, 160 insertions(+), 73 deletions(-) (limited to 'lib') diff --git a/lib/compound.rb b/lib/compound.rb index ce0fdbf..5992ee3 100644 --- a/lib/compound.rb +++ b/lib/compound.rb @@ -40,25 +40,25 @@ module OpenTox # Get InChI # @return [String] InChI string def to_inchi - get(:accept => 'chemical/x-inchi').to_s.chomp if @uri + RestClientWrapper.get(@uri,{},{:accept => 'chemical/x-inchi'}).chomp end # Get (canonical) smiles # @return [String] Smiles string def to_smiles - get(:accept => 'chemical/x-daylight-smiles').chomp + RestClientWrapper.get(@uri,{},{:accept => 'chemical/x-daylight-smiles'}).chomp end # Get sdf # @return [String] SDF string def to_sdf - get(:accept => 'chemical/x-mdl-sdfile').chomp + RestClientWrapper.get(@uri,{},{:accept => 'chemical/x-mdl-sdfile'}).chomp end # Get gif image # @return [image/gif] Image data def to_gif - get("#{CACTUS_URI}#{to_inchi}/image") + RestClientWrapper.get("#{CACTUS_URI}#{to_inchi}/image") end # Get png image @@ -66,7 +66,7 @@ module OpenTox # image = compound.to_png # @return [image/png] Image data def to_png - get(File.join @uri, "image") + RestClientWrapper.get(File.join @uri, "image") end # Get URI of compound image @@ -81,7 +81,7 @@ module OpenTox # @return [String] Compound names def to_names begin - get("#{CACTUS_URI}#{to_inchi}/names").split("\n") + RestClientWrapper.get("#{CACTUS_URI}#{to_inchi}/names").split("\n") rescue "not available" end diff --git a/lib/dataset.rb b/lib/dataset.rb index 33e1571..ed0ccdd 100644 --- a/lib/dataset.rb +++ b/lib/dataset.rb @@ -3,32 +3,128 @@ module OpenTox # Ruby wrapper for OpenTox Dataset Webservices (http://opentox.org/dev/apis/api-1.2/dataset). class Dataset - def data_entries - data_entries = [] - #pull - #@reload = false - begin - self.[](RDF::OT.dataEntry).collect{|data_entry| data_entries << @rdf.to_hash[data_entry] } - rescue + attr_accessor :features, :compounds, :data_entries + + def initialize uri=nil, subjectid=nil + super uri, subjectid + @features = [] + @compounds = [] + @data_entries = [] + append RDF.type, RDF::OT.OrderedDataset + end + + def upload filename + file = File.new filename + RestClientWrapper.put(@uri, {:file => file}, {:subjectid => @subjectid}) + end + + def get + super + @features = [] + @compounds = [] + @data_entries = [] + query = RDF::Query.new do + pattern [:uri, RDF.type, RDF::OT.OrderedDataset] end - begin - # TODO: remove API 1.1 - self.[](RDF::OT1.dataEntry).collect{|data_entry| data_entries << @rdf.to_hash[data_entry] } - rescue + if query.execute(@rdf).first # ordered dataset + query = RDF::Query.new do + pattern [:uri, RDF.type, RDF::OT.Compound] + pattern [:uri, RDF::OLO.index, :idx] + end + @compounds = query.execute(@rdf).sort_by{|s| s.idx}.collect{|s| OpenTox::Compound.new s.uri.to_s} + query = RDF::Query.new do + pattern [:uri, RDF.type, RDF::OT.Feature] + pattern [:uri, RDF::OLO.index, :idx] + end + @features = query.execute(@rdf).sort_by{|s| s.idx}.collect{|s| OpenTox::Feature.new(s.uri.to_s)} + numeric_features = @features.collect{|f| f.get; f[RDF.type].include? RDF::OT.NumericFeature} + @compounds.each_with_index do |compound,i| + query = RDF::Query.new do + pattern [:data_entry, RDF::OLO.index, i] + pattern [:data_entry, RDF::OT.values, :values] + pattern [:values, RDF::OT.feature, :feature] + pattern [:feature, RDF::OLO.index, :feature_idx] + pattern [:values, RDF::OT.value, :value] + end + values = query.execute(@rdf).sort_by{|s| s.feature_idx}.collect do |s| + numeric_features[s.feature_idx] ? s.value.to_s.to_f : s.value.to_s + end + @data_entries << values + end + else + query = RDF::Query.new do + pattern [:uri, RDF.type, RDF::OT.Feature] + end + @features = query.execute(@rdf).collect{|s| OpenTox::Feature.new(s.uri.to_s)} + query = RDF::Query.new do + pattern [:data_entry, RDF::OT.compound, :compound] + end + @compounds = query.execute(@rdf).sort_by{|s| s.data_entry}.collect{|s| OpenTox::Compound.new s.compound.to_s} + numeric_features = @features.collect{|f| f.get; f[RDF.type].include? RDF::OT.NumericFeature} + @compounds.each do |compound| + values = [] + @features.each_with_index do |feature,i| + query = RDF::Query.new do + pattern [:data_entry, RDF::OT.compound, RDF::URI.new(compound.uri)] + pattern [:data_entry, RDF::OT.values, :values] + pattern [:values, RDF::OT.feature, RDF::URI.new(feature.uri)] + pattern [:values, RDF::OT.value, :value] + end + value = query.execute(@rdf).first.value.to_s + value = value.to_f if numeric_features[i] + values << value + end + @data_entries << values + end end - #@reload = true - data_entries end - def compounds - uri = File.join(@uri,"compounds") - RestClientWrapper.get(uri,{},{:accept => "text/uri-list", :subjectid => @subjectid}).split("\n").collect{|uri| OpenTox::Compound.new uri} + def get_metadata + uri = File.join(@uri,"metadata") + begin + parse_ntriples RestClientWrapper.get(uri,{},{:accept => "text/plain", :subjectid => @subjectid}) + rescue # fall back to rdfxml + parse_rdfxml RestClientWrapper.get(uri,{},{:accept => "application/rdf+xml", :subjectid => @subjectid}) + end + metadata end - def features - uri = File.join(@uri,"features") - RestClientWrapper.get(uri,{},{:accept => "text/uri-list", :subjectid => @subjectid}).split("\n").collect{|uri| OpenTox::Feature.new uri} + def << data_entry + compound = data_entry.shift + bad_request_error "Dataset features are empty." unless features + bad_request_error "data_entry size does not match features size." unless data_entry.size == features.size + bad_request_error "First data_entry is not a OpenTox::Compound" unless compound.class == OpenTox::Compound + @compounds << compound + @data_entries << data_entry end + RDF_FORMATS.each do |format| + + # redefine rdf serialization methods + send :define_method, "to_#{format}".to_sym do + # TODO: check, might affect appending to unordered datasets + features.each_with_index do |feature,i| + @rdf << [RDF::URI.new(feature.uri), RDF::URI.new(RDF.type), RDF::URI.new(RDF::OT.Feature)] + @rdf << [RDF::URI.new(feature.uri), RDF::URI.new(RDF::OLO.index), RDF::Literal.new(i)] + end + compounds.each_with_index do |compound,i| + @rdf << [RDF::URI.new(compound.uri), RDF::URI.new(RDF.type), RDF::URI.new(RDF::OT.Compound)] + @rdf << [RDF::URI.new(compound.uri), RDF::URI.new(RDF::OLO.index), RDF::Literal.new(i)] + data_entry_node = RDF::Node.new + @rdf << [RDF::URI.new(@uri), RDF::URI.new(RDF::OT.dataEntry), data_entry_node] + @rdf << [data_entry_node, RDF::URI.new(RDF.type), RDF::URI.new(RDF::OT.DataEntry)] + @rdf << [data_entry_node, RDF::URI.new(RDF::OLO.index), RDF::Literal.new(i)] + @rdf << [data_entry_node, RDF::URI.new(RDF::OT.compound), RDF::URI.new(compound.uri)] + data_entries[i].each_with_index do |value,j| + value_node = RDF::Node.new + @rdf << [data_entry_node, RDF::URI.new(RDF::OT.values), value_node] + @rdf << [value_node, RDF::URI.new(RDF::OT.feature), RDF::URI.new(@features[j].uri)] + @rdf << [value_node, RDF::URI.new(RDF::OT.value), RDF::Literal.new(value)] + end + end + super() + end + + end end end diff --git a/lib/error.rb b/lib/error.rb index 64b0fb1..51451e7 100644 --- a/lib/error.rb +++ b/lib/error.rb @@ -10,7 +10,7 @@ class RuntimeError $logger.error "\n"+self.to_turtle end - # define to_ and self.from_ methods for various rdf formats + # define to_ methods for all RuntimeErrors and various rdf formats RDF_FORMATS.each do |format| send :define_method, "to_#{format}".to_sym do @@ -22,7 +22,7 @@ class RuntimeError subject = RDF::Node.new writer << [subject, RDF.type, RDF::OT.ErrorReport] writer << [subject, RDF::OT.actor, @uri.to_s] - writer << [subject, RDF::OT.message, @message.to_s] + writer << [subject, RDF::OT.message, message.to_s] writer << [subject, RDF::OT.statusCode, @http_code] writer << [subject, RDF::OT.errorCode, self.class.to_s] @@ -80,7 +80,6 @@ module OpenTox class RestCallError < Error attr_accessor :request#, :response def initialize message, request, uri - #def initialize request, response, message @request = request #@response = response super 502, message, uri diff --git a/lib/opentox-client.rb b/lib/opentox-client.rb index 8c19225..ad7d3cc 100644 --- a/lib/opentox-client.rb +++ b/lib/opentox-client.rb @@ -2,6 +2,7 @@ require 'rubygems' require "bundler/setup" require 'rdf' require 'rdf/raptor' +require 'rdf/n3' require "rest-client" require 'uri' require 'yaml' @@ -14,6 +15,7 @@ require "securerandom" RDF::OT = RDF::Vocabulary.new 'http://www.opentox.org/api/1.2#' RDF::OT1 = RDF::Vocabulary.new 'http://www.opentox.org/api/1.1#' RDF::OTA = RDF::Vocabulary.new 'http://www.opentox.org/algorithmTypes.owl#' +RDF::OLO = RDF::Vocabulary.new 'http://purl.org/ontology/olo/core#' CLASSES = ["Generic", "Compound", "Feature", "Dataset", "Algorithm", "Model", "Validation", "Task", "Investigation"] RDF_FORMATS = [:rdfxml,:ntriples,:turtle] diff --git a/lib/opentox.rb b/lib/opentox.rb index b0c786a..9225bb0 100644 --- a/lib/opentox.rb +++ b/lib/opentox.rb @@ -14,7 +14,14 @@ module OpenTox # @return [OpenTox] OpenTox object def initialize uri=nil, subjectid=nil @rdf = RDF::Graph.new - uri ? @uri = uri.to_s.chomp : @uri = RDF::Node.uuid.to_s + if uri + @uri = uri.to_s.chomp + else + service = self.class.to_s.split('::').last.downcase + service_uri = eval("$#{service}[:uri]") + bad_request_error "$#{service}[:uri] variable not set. Please set $#{service}[:uri] or use an explicit uri as first constructor argument " unless service_uri + @uri = File.join service_uri, SecureRandom.uuid + end append RDF.type, eval("RDF::OT."+self.class.to_s.split('::').last) append RDF::DC.date, DateTime.now @subjectid = subjectid @@ -24,7 +31,6 @@ module OpenTox # @return [Hash] Object metadata def metadata # return plain strings instead of RDF objects - #puts @rdf.to_hash @rdf.to_hash[RDF::URI.new(@uri)].inject({}) { |h, (predicate, values)| h[predicate.to_s] = values.collect{|v| v.to_s}; h } end @@ -32,6 +38,7 @@ module OpenTox # @param [String] Predicate URI # @return [Array, String] Predicate value(s) def [](predicate) + return nil if metadata[predicate.to_s].nil? metadata[predicate.to_s].size == 1 ? metadata[predicate.to_s].first : metadata[predicate.to_s] end @@ -55,30 +62,30 @@ module OpenTox # Get object from webservice def get parse_ntriples RestClientWrapper.get(@uri,{},{:accept => "text/plain", :subjectid => @subjectid}) - rescue # fall back to rdfxml - parse_rdfxml RestClientWrapper.get(@uri,{},{:accept => "application/rdf+xml", :subjectid => @subjectid}) + #rescue # fall back to rdfxml + #parse_rdfxml RestClientWrapper.get(@uri,{},{:accept => "application/rdf+xml", :subjectid => @subjectid}) end # Post object to webservice def post service_uri RestClientWrapper.post service_uri, to_ntriples, { :content_type => "text/plain", :subjectid => @subjectid} - rescue # fall back to rdfxml - RestClientWrapper.post service_uri, to_rdfxml, { :content_type => "application/rdf+xml", :subjectid => @subjectid} + #rescue # fall back to rdfxml + #RestClientWrapper.post service_uri, to_rdfxml, { :content_type => "application/rdf+xml", :subjectid => @subjectid} end # Save object at webservice def put append RDF::DC.modified, DateTime.now - begin + #begin RestClientWrapper.put @uri.to_s, self.to_ntriples, { :content_type => "text/plain", :subjectid => @subjectid} - rescue # fall back to rdfxml - RestClientWrapper.put @uri.to_s, self.to_rdfxml, { :content_type => "application/rdf+xml", :subjectid => @subjectid} - end + #rescue # fall back to rdfxml + #RestClientWrapper.put @uri.to_s, self.to_rdfxml, { :content_type => "application/rdf+xml", :subjectid => @subjectid} + #end end # Delete object at webservice def delete - @response = RestClientWrapper.delete(@uri.to_s,nil,{:subjectid => @subjectid}) + RestClientWrapper.delete(@uri.to_s,nil,{:subjectid => @subjectid}) end RDF_FORMATS.each do |format| @@ -100,42 +107,20 @@ module OpenTox end end - # class methods - module ClassMethods - - def all service_uri, subjectid=nil - uris = RestClientWrapper.get(service_uri, {}, :accept => 'text/uri-list').split("\n").compact - uris.collect{|uri| URI.task?(service_uri) ? from_uri(uri, subjectid, false) : from_uri(uri, subjectid)} + def to_turtle # redefine to use prefixes (not supported by RDF::Writer) + prefixes = {:rdf => "http://www.w3.org/1999/02/22-rdf-syntax-ns#"} + ['OT', 'DC', 'XSD', 'OLO'].each{|p| prefixes[p.downcase.to_sym] = eval("RDF::#{p}.to_s") } + turtle = RDF::N3::Writer.for(:turtle).buffer(:prefixes => prefixes) do |writer| + @rdf.each{|statement| writer << statement} end + end - def from_file service_uri, filename, subjectid=nil - file = File.new filename - # sdf files are incorrectly detected - file.mime_type = "chemical/x-mdl-sdfile" if File.extname(filename) == ".sdf" - from_uri RestClientWrapper.post(service_uri, {:file => file}, {:subjectid => subjectid, :content_type => file.mime_type, :accept => "text/uri-list"}), subjectid + {:title => RDF::DC.title, :dexcription => RDF::DC.description}.each do |method,predicate| + send :define_method, method do + self.[](predicate) end - - private - def from_uri uri, subjectid=nil, wait=true - - uri.chomp! - # TODO add waiting task - if URI.task?(uri) and wait - t = OpenTox::Task.new(uri) - t.wait - uri = t.resultURI - end - - # guess class from uri, this is potentially unsafe, but polling metadata from large uri lists is way too slow (and not all service provide RDF.type in their metadata) - result = CLASSES.collect{|s| s if uri =~ /#{s.downcase}/}.compact - if result.size == 1 - klass = result.first - else - klass = OpenTox::Generic.new(uri)[RDF.type] - internal_server_error "Cannot determine class from URI '#{uri} (Candidate classes are #{result.inspect}) or matadata." unless klass - end - # initialize with/without subjectid - subjectid ? eval("#{self}.new(\"#{uri}\", \"#{subjectid}\")") : eval("#{self}.new(\"#{uri}\")") + send :define_method, "#{method}=" do |value| + self.[]=(predicate,value) end end @@ -143,7 +128,12 @@ module OpenTox CLASSES.each do |klass| c = Class.new do include OpenTox - extend OpenTox::ClassMethods + #extend OpenTox::ClassMethods + + def self.all service_uri, subjectid=nil + uris = RestClientWrapper.get(service_uri, {}, :accept => 'text/uri-list').split("\n").compact + uris.collect{|uri| self.new(uri, subjectid)} + end end OpenTox.const_set klass,c end -- cgit v1.2.3