summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/compound.rb12
-rw-r--r--lib/dataset.rb133
-rw-r--r--lib/error.rb5
-rw-r--r--lib/opentox-client.rb2
-rw-r--r--lib/opentox.rb189
-rw-r--r--lib/overwrite.rb18
-rw-r--r--lib/task.rb27
7 files changed, 247 insertions, 139 deletions
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 8032533..a6c22d0 100644
--- a/lib/dataset.rb
+++ b/lib/dataset.rb
@@ -3,27 +3,128 @@ module OpenTox
# Ruby wrapper for OpenTox Dataset Webservices (http://opentox.org/dev/apis/api-1.2/dataset).
class Dataset
- def data_entries
- # TODO fix for api 1.2
- data_entries = []
- pull
- @reload = false
- metadata[RDF::OT1.dataEntry].collect{|data_entry|
- data_entries << @rdf.to_hash[data_entry]
- }
- @reload = true
- data_entries
+ 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, wait=true
+ uri = RestClientWrapper.put(@uri, {:file => File.new(filename)}, {:subjectid => @subjectid})
+ OpenTox::Task.new(uri).wait if URI.task?(uri) and wait
+ end
+
+ def get
+ super
+ @features = []
+ @compounds = []
+ @data_entries = []
+ query = RDF::Query.new do
+ pattern [:uri, RDF.type, RDF::OT.OrderedDataset]
+ end
+ 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
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 f79b51b..2e0f05a 100644
--- a/lib/opentox.rb
+++ b/lib/opentox.rb
@@ -4,7 +4,7 @@ $logger.level = Logger::DEBUG
module OpenTox
- attr_accessor :uri, :subjectid, :rdf, :response, :reload
+ attr_accessor :uri, :subjectid, :rdf
# Ruby interface
@@ -13,44 +13,86 @@ module OpenTox
# @param [optional,String] subjectid
# @return [OpenTox] OpenTox object
def initialize uri=nil, subjectid=nil
- @uri = uri.to_s.chomp
- @subjectid = subjectid
- @reload = true
@rdf = RDF::Graph.new
+ 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
end
- # Load metadata from service
- def pull
- 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})
+ # Object metadata
+ # @return [Hash] Object metadata
+ def metadata
+ # return plain strings instead of RDF objects
+ @rdf.to_hash[RDF::URI.new(@uri)].inject({}) { |h, (predicate, values)| h[predicate.to_s] = values.collect{|v| v.to_s}; h }
end
- # Get object metadata
- # @return [Hash] Metadata
- def metadata
- pull if @reload # force update
- @rdf.to_hash[RDF::URI.new(@uri)]
+ # Metadata values
+ # @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
+
+ # Set object metadata
+ # @param [String] Predicate URI
+ # @param [Array, String] Predicate value(s)
+ def []=(predicate,values)
+ @rdf.delete [RDF::URI.new(@uri.to_s),RDF::URI.new(predicate.to_s),nil]
+ append predicate.to_s, values
+ end
+
+ # Append object metadata
+ # @param [String] Predicate URI
+ # @param [Array, String] Predicate value(s)
+ def append(predicate,values)
+ uri = RDF::URI.new @uri
+ predicate = RDF::URI.new predicate
+ [values].flatten.each { |value| @rdf << [uri, predicate, value] }
end
- # Get metadata values
- # @param [RDF] Key from RDF Vocabularies
- # @return [Array] Values for supplied key
- def [](key)
- pull if @reload # force update
- result = @rdf.query([RDF::URI.new(@uri),key,nil]).collect{|statement| statement.object}
- # TODO: convert to OpenTox objects??
- return nil if result and result.empty?
- return result.first.to_s if result.size == 1
- return result.collect{|r| r.to_s}
- result
+ # Get object from webservice
+ def get wait=true
+ response = RestClientWrapper.get(@uri,{},{:accept => "text/plain", :subjectid => @subjectid})
+ if URI.task?(response) and wait
+ t = OpenTox::Task.new(uri).wait
+ response = RestClientWrapper.get(t.resultURI,{},{:accept => "text/plain", :subjectid => @subjectid})
+ end
+ parse_ntriples response
+ #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, wait=true
+ uri = RestClientWrapper.post service_uri, to_ntriples, { :content_type => "text/plain", :subjectid => @subjectid}
+ OpenTox::Task.new(uri).wait if URI.task?(uri) and wait
+ #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 wait=true
+ append RDF::DC.modified, DateTime.now
+ #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
+ OpenTox::Task.new(uri).wait if URI.task?(uri) and wait
end
- # Save object at service
- def save
- put self.to_ntriples, { :content_type => "text/plain"}
- rescue # fall back to rdfxml
- put self.to_rdfxml, { :content_type => "application/rdf+xml"}
+ # Delete object at webservice
+ def delete
+ RestClientWrapper.delete(@uri.to_s,nil,{:subjectid => @subjectid})
end
RDF_FORMATS.each do |format|
@@ -72,78 +114,20 @@ module OpenTox
end
end
- def to_yaml
- @rdf.to_hash.to_yaml
- end
-
- def to_json
- to_hash.to_json
- end
-
- # REST API
- def get headers={}
- headers[:subjectid] ||= @subjectid
- headers[:accept] ||= 'application/rdf+xml'
- @response = RestClientWrapper.get @uri, {}, headers
- end
-
- def post payload={}, headers={}
- headers[:subjectid] ||= @subjectid
- headers[:accept] ||= 'application/rdf+xml'
- @response = RestClientWrapper.post(@uri.to_s, payload, headers)
- end
-
- def put payload={}, headers={}
- headers[:subjectid] ||= @subjectid
- headers[:accept] ||= 'application/rdf+xml'
- @response = RestClientWrapper.put(@uri.to_s, payload, headers)
- end
-
- def delete headers={}
- headers[:subjectid] ||= @subjectid
- @response = RestClientWrapper.delete(@uri.to_s,nil,headers)
- 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 create service_uri, subjectid=nil
- #uri = File.join(service_uri,SecureRandom.uuid)
- uri = RestClientWrapper.post(service_uri, {}, {:accept => 'text/uri-list', :subjectid => subjectid})
- URI.task?(service_uri) ? from_uri(uri, subjectid, false) : from_uri(uri, subjectid)
+ {:title => RDF::DC.title, :dexcription => RDF::DC.description}.each do |method,predicate|
+ send :define_method, method do
+ self.[](predicate)
end
-
- def from_file service_uri, filename, subjectid=nil
- file = File.new filename
- from_uri RestClientWrapper.post(service_uri, {:file => file}, {:subjectid => subjectid, :content_type => file.mime_type, :accept => "text/uri-list"}), subjectid
- 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
@@ -151,7 +135,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
diff --git a/lib/overwrite.rb b/lib/overwrite.rb
index d98769e..137fec8 100644
--- a/lib/overwrite.rb
+++ b/lib/overwrite.rb
@@ -1,3 +1,21 @@
+class Object
+ # An object is blank if it's false, empty, or a whitespace string.
+ # For example, "", " ", +nil+, [], and {} are all blank.
+ def blank?
+ respond_to?(:empty?) ? empty? : !self
+ end
+
+ def numeric?
+ true if Float(self) rescue false
+ end
+end
+
+module Enumerable
+ def duplicates
+ inject({}) {|h,v| h[v]=h[v].to_i+1; h}.reject{|k,v| v==1}.keys
+ end
+end
+
class String
def underscore
self.gsub(/::/, '/').
diff --git a/lib/task.rb b/lib/task.rb
index 2ca481d..0b0aea2 100644
--- a/lib/task.rb
+++ b/lib/task.rb
@@ -8,13 +8,11 @@ module OpenTox
def self.create service_uri, subjectid=nil, params={}
- uri = RDF::URI.new File.join(service_uri,SecureRandom.uuid)
+ uri = File.join(service_uri,SecureRandom.uuid)
task = Task.new uri, subjectid
- task.rdf << RDF::Statement.new(uri, RDF.type, RDF::OT.Task)
- task.rdf << RDF::Statement.new(uri, RDF::DC.date, RDF::Literal.new(DateTime.now))
- task.rdf << RDF::Statement.new(uri, RDF::OT.hasStatus, RDF::Literal.new("Running"))
- params.each {|k,v| task.rdf << RDF::Statement.new(uri, k, v)}
- task.save
+ task[RDF::OT.hasStatus] = "Running"
+ params.each { |k,v| task[k] = v }
+ task.put false
pid = fork do
begin
result_uri = yield
@@ -53,24 +51,23 @@ module OpenTox
end
def description
- pull
- self.[](RDF::DC.description).uniq.first
+ self.[](RDF::DC.description)
end
def creator
- pull
- self.[](RDF::DC.creator).uniq.first
+ self.[](RDF::DC.creator)
end
def cancel
kill
- RestClientWrapper.put(File.join(@uri,'Cancelled'),{})
+ self.[]=(RDF::OT.hasStatus, "Cancelled")
+ put false
end
def completed(uri)
- #puts uri
- #not_found_error "Result URI \"#{uri}\" does not exist." unless URI.accessible? uri, @subjectid
- RestClientWrapper.put(File.join(@uri,'Completed'),{:resultURI => uri})
+ self.[]=(RDF::OT.resultURI, uri)
+ self.[]=(RDF::OT.hasStatus, "Completed")
+ put false
end
# waits for a task, unless time exceeds or state is no longer running
@@ -85,6 +82,7 @@ module OpenTox
dur = [[(Time.new - start_time)/20.0,0.3].max,300.0].min
time_out_error "max wait time exceeded ("+DEFAULT_TASK_MAX_DURATION.to_s+"sec), task: '"+@uri.to_s+"'" if (Time.new > due_to_time)
end
+ get
end
end
@@ -109,6 +107,7 @@ module OpenTox
[:hasStatus, :resultURI, :finished_at].each do |method|
define_method method do
+ get
response = self.[](RDF::OT[method])
response = self.[](RDF::OT1[method]) unless response # API 1.1 compatibility
response