opentox-ruby-api-wrapper 1.4.0 → 1.5.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.
@@ -0,0 +1,170 @@
1
+
2
+
3
+ module OpenTox
4
+
5
+ #PENDING: implement ot error api, move to own file
6
+ class Error
7
+
8
+ attr_accessor :code, :body, :uri, :payload, :headers
9
+
10
+ def initialize(code, body, uri, payload, headers)
11
+ self.code = code
12
+ self.body = body
13
+ self.uri = uri
14
+ self.payload = payload
15
+ self.headers = headers
16
+ end
17
+
18
+ def self.parse(error_array_string)
19
+ begin
20
+ err = YAML.load(error_array_string)
21
+ if err and err.is_a?(Array) and err.size>0 and err[0].is_a?(Error)
22
+ return err
23
+ else
24
+ return nil
25
+ end
26
+ rescue
27
+ return nil
28
+ end
29
+ end
30
+
31
+ end
32
+
33
+ class WrapperResult < String
34
+ attr_accessor :content_type, :code
35
+ end
36
+
37
+ class RestClientWrapper
38
+
39
+ def self.get(uri, headers=nil, wait=true)
40
+ execute( "get", uri, headers, nil, wait)
41
+ end
42
+
43
+ def self.post(uri, headers, payload=nil, wait=true)
44
+ execute( "post", uri, headers, payload, wait )
45
+ end
46
+
47
+ def self.put(uri, headers, payload=nil )
48
+ execute( "put", uri, headers, payload )
49
+ end
50
+
51
+ def self.delete(uri, headers=nil)
52
+ execute( "delete", uri, headers, nil)
53
+ end
54
+
55
+ def self.raise_uri_error(error_msg, uri, headers=nil, payload=nil)
56
+ do_halt( "-", error_msg, uri, headers, payload )
57
+ end
58
+
59
+ private
60
+ def self.execute( rest_call, uri, headers, payload=nil, wait=true )
61
+
62
+ do_halt 400,"uri is null",uri,headers,payload unless uri
63
+ do_halt 400,"not a uri",uri,headers,payload unless Utils.is_uri?(uri)
64
+ do_halt 400,"headers are no hash",uri,headers,payload unless headers==nil or headers.is_a?(Hash)
65
+ do_halt 400,"nil headers for post not allowed, use {}",uri,headers,payload if rest_call=="post" and headers==nil
66
+ headers.each{ |k,v| headers.delete(k) if v==nil } if headers #remove keys with empty values, as this can cause problems
67
+
68
+ begin
69
+ #LOGGER.debug "RestCall: "+rest_call.to_s+" "+uri.to_s+" "+headers.inspect
70
+ resource = RestClient::Resource.new(uri,{:timeout => 60}) #, :user => @@users[:users].keys[0], :password => @@users[:users].values[0]})
71
+ if payload
72
+ result = resource.send(rest_call, payload, headers)
73
+ elsif headers
74
+ result = resource.send(rest_call, headers)
75
+ else
76
+ result = resource.send(rest_call)
77
+ end
78
+
79
+ # result is a string, with the additional fields content_type and code
80
+ res = WrapperResult.new(result.body)
81
+ res.content_type = result.headers[:content_type]
82
+ raise "content-type not set" unless res.content_type
83
+ res.code = result.code
84
+
85
+ return res if res.code==200 || !wait
86
+
87
+ while (res.code==201 || res.code==202)
88
+ res = wait_for_task(res, uri)
89
+ end
90
+ raise "illegal status code: '"+res.code.to_s+"'" unless res.code==200
91
+ return res
92
+
93
+ rescue RestClient::RequestFailed => ex
94
+ do_halt ex.http_code,ex.http_body,uri,headers,payload
95
+ rescue RestClient::RequestTimeout => ex
96
+ do_halt 408,ex.message,uri,headers,payload
97
+ rescue => ex
98
+ #raise ex
99
+ #raise "'"+ex.message+"' uri: "+uri.to_s
100
+ begin
101
+ code = ex.http_code
102
+ msg = ex.http_body
103
+ rescue
104
+ code = 500
105
+ msg = ex.to_s
106
+ end
107
+ do_halt code,msg,uri,headers,payload
108
+ end
109
+ end
110
+
111
+ def self.wait_for_task( res, base_uri )
112
+
113
+ task = nil
114
+ case res.content_type
115
+ when /application\/rdf\+xml|text\/x-yaml/
116
+ task = OpenTox::Task.from_data(res, res.content_type, res.code, base_uri)
117
+ when /text\//
118
+ raise "uri list has more than one entry, should be a task" if res.content_type=~/text\/uri-list/ and
119
+ res.split("\n").size > 1 #if uri list contains more then one uri, its not a task
120
+ task = OpenTox::Task.find(res.to_s) if Utils.task_uri?(res)
121
+ else
122
+ raise "unknown content-type for task: '"+res.content_type.to_s+"'" #+"' content: "+res[0..200].to_s
123
+ end
124
+
125
+ LOGGER.debug "result is a task '"+task.uri.to_s+"', wait for completion"
126
+ task.wait_for_completion
127
+ raise task.description unless task.completed? # maybe task was cancelled / error
128
+
129
+ res = WrapperResult.new task.resultURI
130
+ res.code = task.http_code
131
+ res.content_type = "text/uri-list"
132
+ return res
133
+ end
134
+
135
+ def self.do_halt( code, body, uri, headers, payload=nil )
136
+
137
+ #build error
138
+ causing_errors = Error.parse(body)
139
+ if causing_errors
140
+ error = causing_errors + [Error.new(code, "subsequent error", uri, payload, headers)]
141
+ else
142
+ error = [Error.new(code, body, uri, payload, headers)]
143
+ end
144
+
145
+ # #debug utility: write error to file
146
+ # error_dir = "/tmp/ot_errors"
147
+ # FileUtils.mkdir(error_dir) unless File.exist?(error_dir)
148
+ # raise "could not create error dir" unless File.exist?(error_dir) and File.directory?(error_dir)
149
+ # file_name = "error"
150
+ # time=Time.now.strftime("%m.%d.%Y-%H:%M:%S")
151
+ # count = 1
152
+ # count+=1 while File.exist?(File.join(error_dir,file_name+"_"+time+"_"+count.to_s))
153
+ # File.new(File.join(error_dir,file_name+"_"+time+"_"+count.to_s),"w").puts(body)
154
+
155
+ # handle error
156
+ # we are either in a task, or in sinatra
157
+ # PENDING: always return yaml for now
158
+
159
+ if $self_task #this global var in Task.as_task to mark that the current process is running in a task
160
+ raise error.to_yaml # the error is caught, logged, and task state is set to error in Task.as_task
161
+ #elsif $sinatra #else halt sinatra
162
+ #$sinatra.halt(502,error.to_yaml)
163
+ elsif defined?(halt)
164
+ halt(502,error.to_yaml)
165
+ else #for testing purposes (if classes used directly)
166
+ raise error.to_yaml
167
+ end
168
+ end
169
+ end
170
+ end
data/lib/spork.rb CHANGED
@@ -37,6 +37,7 @@ module Spork
37
37
  def self.spork(options={})
38
38
  logger = options[:logger]
39
39
  logger.debug "spork> parent PID = #{Process.pid}" if logger
40
+
40
41
  child = fork do
41
42
  begin
42
43
  start = Time.now
@@ -52,6 +53,7 @@ module Spork
52
53
  yield
53
54
 
54
55
  rescue => ex
56
+ #raise ex
55
57
  logger.error "spork> Exception in child[#{Process.pid}] - #{ex.class}: #{ex.message}" if logger
56
58
  ensure
57
59
  logger.info "spork> child[#{Process.pid}] took #{Time.now - start} sec" if logger
data/lib/task.rb CHANGED
@@ -1,97 +1,170 @@
1
1
  LOGGER.progname = File.expand_path(__FILE__)
2
2
 
3
+ $self_task=nil
4
+
3
5
  module OpenTox
4
6
 
5
7
  class Task
6
8
 
7
- attr_accessor :uri
8
-
9
- def initialize(uri)
10
- @uri = uri.chomp
11
- end
12
-
13
- def self.create
14
- resource = RestClient::Resource.new(@@config[:services]["opentox-task"], :user => @@users[:users].keys[0], :password => @@users[:users].values[0])
15
- uri = resource.post({}).chomp
16
- Task.new(uri.chomp)
17
- end
18
-
9
+ # due_to_time is only set in local tasks
10
+ TASK_ATTRIBS = [ :uri, :date, :title, :creator, :description, :hasStatus, :percentageCompleted, :resultURI, :due_to_time ]
11
+ TASK_ATTRIBS.each{ |a| attr_accessor(a) }
12
+ attr_accessor :http_code
13
+
14
+ private
15
+ def initialize(uri)
16
+ @uri = uri.to_s.strip
17
+ end
18
+
19
+ # create is private now, use OpenTox::Task.as_task
20
+ def self.create(max_duration)
21
+ task_uri = RestClientWrapper.post(@@config[:services]["opentox-task"], {:max_duration => max_duration}, nil, false).to_s
22
+ Task.find(task_uri.chomp)
23
+ end
24
+
25
+ public
19
26
  def self.find(uri)
20
- Task.new(uri)
21
- end
22
-
23
- def self.base_uri
24
- @@config[:services]["opentox-task"]
25
- end
26
-
27
- def self.all
28
- task_uris = RestClient.get(@@config[:services]["opentox-task"]).chomp.split(/\n/)
29
- task_uris.collect{|uri| Task.new(uri)}
30
- end
31
-
32
- def created_at
33
- RestClient.get File.join(@uri, 'created_at')
34
- end
35
-
36
- def finished_at
37
- RestClient.get File.join(@uri, 'finished_at')
38
- end
39
-
40
- def status
41
- RestClient.get File.join(@uri, 'status')
42
- end
43
-
44
- def resource
45
- RestClient.get File.join(@uri, 'resource')
46
- end
47
-
48
- def started
49
- LOGGER.info File.join(@uri,'started')
50
- resource = RestClient::Resource.new(File.join(@uri,'started'), :user => @@users[:users].keys[0], :password => @@users[:users].values[0])
51
- resource.put({})
52
- end
53
-
27
+ task = Task.new(uri)
28
+ task.reload
29
+ return task
30
+ end
31
+
32
+ def self.from_data(data, content_type, code, base_uri)
33
+ task = Task.new(nil)
34
+ task.http_code = code
35
+ task.reload_from_data(data, content_type, base_uri)
36
+ return task
37
+ end
38
+
39
+ def reload
40
+ result = RestClientWrapper.get(uri, {:accept => 'application/rdf+xml'}, false)#'application/x-yaml'})
41
+ @http_code = result.code
42
+ reload_from_data(result, result.content_type, uri)
43
+ end
44
+
45
+ def reload_from_data( data, content_type, base_uri )
46
+ case content_type
47
+ when /yaml/
48
+ task = YAML.load data
49
+ TASK_ATTRIBS.each do |a|
50
+ raise "task yaml data invalid, key missing: "+a.to_s unless task.has_key?(a)
51
+ send("#{a.to_s}=".to_sym,task[a])
52
+ end
53
+ when /application\/rdf\+xml/
54
+ owl = OpenTox::Owl.from_data(data,base_uri,"Task")
55
+ self.uri = owl.uri
56
+ (TASK_ATTRIBS-[:uri]).each{|a| self.send("#{a.to_s}=".to_sym, owl.get(a.to_s))}
57
+ else
58
+ raise "content type for tasks not supported: "+content_type.to_s
59
+ end
60
+ raise "uri is null after loading" unless @uri and @uri.to_s.strip.size>0
61
+ end
62
+
54
63
  def cancel
55
- resource = RestClient::Resource.new(File.join(@uri,'cancelled'), :user => @@users[:users].keys[0], :password => @@users[:users].values[0])
56
- resource.put({})
64
+ RestClientWrapper.put(File.join(@uri,'Cancelled'))
65
+ reload
57
66
  end
58
67
 
59
68
  def completed(uri)
60
- resource = RestClient::Resource.new(File.join(@uri,'completed'), :user => @@users[:users].keys[0], :password => @@users[:users].values[0])
61
- resource.put :resource => uri
69
+ RestClientWrapper.put(File.join(@uri,'Completed'),{:resultURI => uri})
70
+ reload
62
71
  end
63
72
 
64
- def failed
65
- resource = RestClient::Resource.new(File.join(@uri,'failed'), :user => @@users[:users].keys[0], :password => @@users[:users].values[0])
66
- resource.put({})
73
+ def error(description)
74
+ RestClientWrapper.put(File.join(@uri,'Error'),{:description => description})
75
+ reload
67
76
  end
77
+
78
+ def pid=(pid)
79
+ RestClientWrapper.put(File.join(@uri,'pid'), {:pid => pid})
80
+ end
68
81
 
69
- def parent=(task)
70
- #RestClient.put File.join(@uri,'parent'), {:uri => task.uri}
71
- resource = RestClient::Resource.new(File.join(@uri,'parent'), :user => @@users[:users].keys[0], :password => @@users[:users].values[0])
72
- LOGGER.debug "task.rb: #{resource}"
73
- resource.put :uri => task.uri
74
- end
75
-
76
- def pid=(pid)
77
- resource = RestClient::Resource.new(File.join(@uri,'pid'), :user => @@users[:users].keys[0], :password => @@users[:users].values[0])
78
- resource.put :pid => pid
79
- end
82
+ def running?
83
+ @hasStatus.to_s == 'Running'
84
+ end
80
85
 
81
86
  def completed?
82
- self.status.to_s == 'completed'
83
- end
84
-
85
- def failed?
86
- self.status.to_s == 'failed'
87
- end
88
-
89
- def wait_for_completion(dur=0.1)
90
- until self.completed? or self.failed?
87
+ @hasStatus.to_s == 'Completed'
88
+ end
89
+
90
+ def error?
91
+ @hasStatus.to_s == 'Error'
92
+ end
93
+
94
+ # waits for a task, unless time exceeds or state is no longer running
95
+ def wait_for_completion(dur=0.3)
96
+
97
+ if (@uri.match(@@config[:services]["opentox-task"]))
98
+ due_to_time = Time.parse(@due_to_time)
99
+ running_time = due_to_time - Time.parse(@date)
100
+ else
101
+ # the date of the external task cannot be trusted, offest to local time might be to big
102
+ due_to_time = Time.new + EXTERNAL_TASK_MAX_DURATION
103
+ running_time = EXTERNAL_TASK_MAX_DURATION
104
+ end
105
+ LOGGER.debug "start waiting for task "+@uri.to_s+" at: "+Time.new.to_s+", waiting at least until "+due_to_time.to_s
106
+
107
+ while self.running?
91
108
  sleep dur
109
+ reload
110
+ check_state
111
+ if (Time.new > due_to_time)
112
+ raise "max wait time exceeded ("+running_time.to_s+"sec), task: '"+@uri.to_s+"'"
113
+ end
92
114
  end
93
- end
94
-
115
+
116
+ LOGGER.debug "task no longer running: "+@uri.to_s+", result: "+@resultURI.to_s
117
+ end
118
+
119
+ def check_state
120
+ begin
121
+ raise "illegal task state, task is completed, resultURI is no URI: '"+@resultURI.to_s+
122
+ "'" unless @resultURI and Utils.is_uri?(@resultURI) if completed?
123
+
124
+ if @http_code == 202
125
+ raise "illegal task state, code is 202, but hasStatus is not Running: '"+@hasStatus+"'" unless running?
126
+ elsif @http_code == 201
127
+ raise "illegal task state, code is 201, but hasStatus is not Completed: '"+@hasStatus+"'" unless completed?
128
+ raise "illegal task state, code is 201, resultURI is no task-URI: '"+@resultURI.to_s+
129
+ "'" unless @resultURI and Utils.task_uri?(@resultURI)
130
+ end
131
+ rescue => ex
132
+ RestClientWrapper.raise_uri_error(ex.message, @uri)
133
+ end
134
+ end
135
+
136
+ # returns the task uri
137
+ # catches halts and exceptions, task state is set to error then
138
+ def self.as_task(max_duration=DEFAULT_TASK_MAX_DURATION)
139
+ #return yield nil
140
+
141
+ task = OpenTox::Task.create(max_duration)
142
+ task_pid = Spork.spork(:logger => LOGGER) do
143
+ LOGGER.debug "Task #{task.uri} started #{Time.now}"
144
+ $self_task = task
145
+
146
+ begin
147
+ result = catch(:halt) do
148
+ yield task
149
+ end
150
+ # catching halt, set task state to error
151
+ if result && result.is_a?(Array) && result.size==2 && result[0]>202
152
+ LOGGER.error "task was halted: "+result.inspect
153
+ task.error(result[1])
154
+ return
155
+ end
156
+ LOGGER.debug "Task #{task.uri} done #{Time.now} -> "+result.to_s
157
+ task.completed(result)
158
+ rescue => ex
159
+ LOGGER.error "task failed: "+ex.message
160
+ LOGGER.error ": "+ex.backtrace.join("\n")
161
+ task.error(ex.message)
162
+ end
163
+ end
164
+ task.pid = task_pid
165
+ LOGGER.debug "Started task: "+task.uri.to_s
166
+ task.uri
167
+ end
95
168
  end
96
169
 
97
170
  end
@@ -1,13 +1,25 @@
1
1
  # Example configuration for OpenTox, please adjust to your settings
2
2
  #
3
+ # Database setup:
4
+ #
5
+ # Example MySql:
6
+ #
7
+ # :database:
8
+ # :adapter: mysql
9
+ # :database: production
10
+ # :username: production
11
+ # :password: your_password
12
+ # :host: localhost
13
+ #
3
14
  # Example 1: Using external test services
4
15
  #
5
16
  # :services:
6
- # opentox-compound: "http://webservices.in-silico.ch/test/compound/"
7
- # opentox-dataset: "http://webservices.in-silico.ch/test/dataset/"
8
- # opentox-algorithm: "http://webservices.in-silico.ch/test/algorithm/"
9
- # opentox-model: "http://webservices.in-silico.ch/test/model/"
10
- # opentox-task: "http://webservices.in-silico.ch/test/task/"
17
+ # opentox-compound: "http://webservices.in-silico.ch/compound/"
18
+ # opentox-dataset: "http://webservices.in-silico.ch/dataset/"
19
+ # opentox-algorithm: "http://webservices.in-silico.ch/algorithm/"
20
+ # opentox-model: "http://webservices.in-silico.ch/model/"
21
+ # opentox-task: "http://webservices.in-silico.ch/task/"
22
+ # opentox-validation: "http://opentox.informatik.uni-freiburg.de/validation/"
11
23
  #
12
24
  # Example 2: Using local services
13
25
  # :base_dir: /home/ch/webservices
@@ -18,3 +30,39 @@
18
30
  # opentox-algorithm: "http://localhost:4002/"
19
31
  # opentox-model: "http://localhost:4003/"
20
32
  # opentox-task: "http://localhost:4004/"
33
+ # opentox-validation: "http://opentox.informatik.uni-freiburg.de/validation/"
34
+ #
35
+ # Accept headers:
36
+ #
37
+ # :accept_headers:
38
+ # opentox-compound:
39
+ # - "chemical/x-daylight-smiles"
40
+ # - "chemical/x-inchi"
41
+ # - "chemical/x-mdl-sdfile"
42
+ # - "image/gif"
43
+ # - "text/plain"
44
+ # opentox-dataset:
45
+ # - "application/x-yaml"
46
+ # - "text/x-yaml"
47
+ # - "application/rdf+xml"
48
+ # opentox-algorithm:
49
+ # - "application/x-yaml"
50
+ # - "text/x-yaml"
51
+ # - "application/rdf+xml"
52
+ # opentox-model:
53
+ # - "application/x-yaml"
54
+ # - "text/x-yaml"
55
+ # - "application/rdf+xml"
56
+ # opentox-task:
57
+ # - "application/x-yaml"
58
+ # - "text/x-yaml"
59
+ # - "application/rdf+xml"
60
+ # opentox-validation:
61
+ # - "application/x-yaml"
62
+ # - "text/x-yaml"
63
+ # - "application/rdf+xml"
64
+
65
+ # Timeouts:
66
+ #
67
+ # :default_task_max_duration: 3600
68
+ # :external_task_max_duration: 3600
data/lib/utils.rb CHANGED
@@ -4,6 +4,38 @@ module OpenTox
4
4
  def self.gauss(sim, sigma = 0.3)
5
5
  x = 1.0 - sim
6
6
  Math.exp(-(x*x)/(2*sigma*sigma))
7
- end
8
- end
7
+ end
8
+
9
+ def self.task_uri?(uri)
10
+ is_uri?(uri) && uri.to_s =~ /task/
11
+ end
12
+
13
+ def self.dataset_uri?(uri)
14
+ is_uri?(uri) && uri.to_s =~ /dataset/
15
+ end
16
+
17
+ def self.model_uri?(uri)
18
+ is_uri?(uri) && uri.to_s =~ /model/
19
+ end
20
+
21
+
22
+ def self.is_uri?(uri)
23
+ return false if uri==nil || uri.to_s.size==0
24
+ begin
25
+ u = URI::parse(uri)
26
+ return (u.scheme!=nil and u.host!=nil)
27
+ rescue URI::InvalidURIError
28
+ return false
29
+ end
30
+ end
31
+ end
32
+
33
+ # ['rubygems', 'rest_client'].each do |r|
34
+ # require r
35
+ # end
36
+ # ["bla", "google.de", "http://google.de"].each do |u|
37
+ # puts u+"? "+Utils.is_uri?(u).to_s
38
+ # end
39
+
9
40
  end
41
+
data/lib/validation.rb CHANGED
@@ -4,8 +4,16 @@ module OpenTox
4
4
  attr_accessor :uri
5
5
 
6
6
  def initialize(params)
7
- resource = RestClient::Resource.new(params[:uri], :user => @@users[:users].keys[0], :password => @@users[:users].values[0])
8
- @uri = resource.post(params).to_s
7
+ #resource = RestClient::Resource.new(params[:uri], :user => @@users[:users].keys[0], :password => @@users[:users].values[0])
8
+ #@uri = resource.post(params).body
9
+ #LOGGER.debug "VALIDATION URI: " + @uri.to_s
10
+ call = "curl -X POST "
11
+ params.each do |k,v|
12
+ call += " -d "+k.to_s+"=\""+URI.encode(v.to_s)+"\"" unless k == :uri
13
+ end
14
+ call += " "+params[:uri]
15
+ LOGGER.debug call
16
+ @uri = `#{call}`
9
17
  end
10
18
 
11
19
  def self.crossvalidation(params)