opentox-ruby 0.0.1
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.
- data/LICENSE +674 -0
- data/README.rdoc +23 -0
- data/Rakefile +87 -0
- data/VERSION +1 -0
- data/bin/opentox-install-debian.sh +105 -0
- data/bin/opentox-install-ubuntu.sh +375 -0
- data/lib/algorithm.rb +82 -0
- data/lib/compound.rb +128 -0
- data/lib/config/config_ru.rb +51 -0
- data/lib/dataset.rb +226 -0
- data/lib/environment.rb +77 -0
- data/lib/helper.rb +26 -0
- data/lib/model.rb +143 -0
- data/lib/opentox.owl +809 -0
- data/lib/overwrite.rb +14 -0
- data/lib/rest_client_wrapper.rb +168 -0
- data/lib/spork.rb +83 -0
- data/lib/task.rb +176 -0
- data/lib/templates/config.yaml +41 -0
- data/lib/validation.rb +20 -0
- metadata +437 -0
data/lib/overwrite.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# class overwrites aka monkey patches
|
2
|
+
# hack: store sinatra in global var to make url_for and halt methods accessible
|
3
|
+
before{ $sinatra = self unless $sinatra }
|
4
|
+
|
5
|
+
class Sinatra::Base
|
6
|
+
# overwriting halt to log halts (!= 202)
|
7
|
+
def halt(*response)
|
8
|
+
LOGGER.error "halt "+response.first.to_s+" "+(response.size>1 ? response[1].to_s : "") if response and response.first and response.first >= 300
|
9
|
+
# orig sinatra code:
|
10
|
+
response = response.first if response.length == 1
|
11
|
+
throw :halt, response
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
@@ -0,0 +1,168 @@
|
|
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.to_s[0..1000]
|
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::RequestTimeout => ex
|
94
|
+
do_halt 408,ex.message,uri,headers,payload
|
95
|
+
rescue => ex
|
96
|
+
#raise ex
|
97
|
+
#raise "'"+ex.message+"' uri: "+uri.to_s
|
98
|
+
begin
|
99
|
+
code = ex.http_code
|
100
|
+
msg = ex.http_body
|
101
|
+
rescue
|
102
|
+
code = 500
|
103
|
+
msg = ex.to_s
|
104
|
+
end
|
105
|
+
do_halt code,msg,uri,headers,payload
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.wait_for_task( res, base_uri )
|
110
|
+
|
111
|
+
task = nil
|
112
|
+
case res.content_type
|
113
|
+
when /application\/rdf\+xml|application\/x-yaml/
|
114
|
+
task = OpenTox::Task.from_data(res, res.content_type, res.code, base_uri)
|
115
|
+
when /text\//
|
116
|
+
raise "uri list has more than one entry, should be a task" if res.content_type=~/text\/uri-list/ and
|
117
|
+
res.split("\n").size > 1 #if uri list contains more then one uri, its not a task
|
118
|
+
task = OpenTox::Task.find(res.to_s) if Utils.task_uri?(res)
|
119
|
+
else
|
120
|
+
raise "unknown content-type for task: '"+res.content_type.to_s+"'" #+"' content: "+res[0..200].to_s
|
121
|
+
end
|
122
|
+
|
123
|
+
LOGGER.debug "result is a task '"+task.uri.to_s+"', wait for completion"
|
124
|
+
task.wait_for_completion
|
125
|
+
raise task.description unless task.completed? # maybe task was cancelled / error
|
126
|
+
|
127
|
+
res = WrapperResult.new task.resultURI
|
128
|
+
res.code = task.http_code
|
129
|
+
res.content_type = "text/uri-list"
|
130
|
+
return res
|
131
|
+
end
|
132
|
+
|
133
|
+
def self.do_halt( code, body, uri, headers, payload=nil )
|
134
|
+
|
135
|
+
#build error
|
136
|
+
causing_errors = Error.parse(body)
|
137
|
+
if causing_errors
|
138
|
+
error = causing_errors + [Error.new(code, "subsequent error", uri, payload, headers)]
|
139
|
+
else
|
140
|
+
error = [Error.new(code, body, uri, payload, headers)]
|
141
|
+
end
|
142
|
+
|
143
|
+
#debug utility: write error to file
|
144
|
+
error_dir = "/tmp/ot_errors"
|
145
|
+
FileUtils.mkdir(error_dir) unless File.exist?(error_dir)
|
146
|
+
raise "could not create error dir" unless File.exist?(error_dir) and File.directory?(error_dir)
|
147
|
+
file_name = "error"
|
148
|
+
time=Time.now.strftime("%m.%d.%Y-%H:%M:%S")
|
149
|
+
count = 1
|
150
|
+
count+=1 while File.exist?(File.join(error_dir,file_name+"_"+time+"_"+count.to_s))
|
151
|
+
File.new(File.join(error_dir,file_name+"_"+time+"_"+count.to_s),"w").puts(body)
|
152
|
+
|
153
|
+
# handle error
|
154
|
+
# we are either in a task, or in sinatra
|
155
|
+
# PENDING: always return yaml for now
|
156
|
+
|
157
|
+
if $self_task #this global var in Task.as_task to mark that the current process is running in a task
|
158
|
+
raise error.to_yaml # the error is caught, logged, and task state is set to error in Task.as_task
|
159
|
+
#elsif $sinatra #else halt sinatra
|
160
|
+
#$sinatra.halt(502,error.to_yaml)
|
161
|
+
elsif defined?(halt)
|
162
|
+
halt(502,error.to_yaml)
|
163
|
+
else #for testing purposes (if classes used directly)
|
164
|
+
raise error.to_yaml
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
data/lib/spork.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
# A way to cleanly handle process forking in Sinatra when using Passenger, aka "sporking some code".
|
2
|
+
# This will allow you to properly execute some code asynchronously, which otherwise does not work correctly.
|
3
|
+
#
|
4
|
+
# Written by Ron Evans
|
5
|
+
# More info at http://deadprogrammersociety.com
|
6
|
+
#
|
7
|
+
# Mostly lifted from the Spawn plugin for Rails (http://github.com/tra/spawn)
|
8
|
+
# but with all of the Rails stuff removed.... cause you are using Sinatra. If you are using Rails, Spawn is
|
9
|
+
# what you need. If you are using something else besides Sinatra that is Rack-based under Passenger, and you are having trouble with
|
10
|
+
# asynch processing, let me know if spork helped you.
|
11
|
+
#
|
12
|
+
module Spork
|
13
|
+
# things to close in child process
|
14
|
+
@@resources = []
|
15
|
+
def self.resources
|
16
|
+
@@resources
|
17
|
+
end
|
18
|
+
|
19
|
+
# set the resource to disconnect from in the child process (when forking)
|
20
|
+
def self.resource_to_close(resource)
|
21
|
+
@@resources << resource
|
22
|
+
end
|
23
|
+
|
24
|
+
# close all the resources added by calls to resource_to_close
|
25
|
+
def self.close_resources
|
26
|
+
@@resources.each do |resource|
|
27
|
+
resource.close if resource && resource.respond_to?(:close) && !resource.closed?
|
28
|
+
end
|
29
|
+
@@resources = []
|
30
|
+
end
|
31
|
+
|
32
|
+
# actually perform the fork... er, spork
|
33
|
+
# valid options are:
|
34
|
+
# :priority => to set the process priority of the child
|
35
|
+
# :logger => a logger object to use from the child
|
36
|
+
# :no_detach => true if you want to keep the child process under the parent control. usually you do NOT want this
|
37
|
+
def self.spork(options={})
|
38
|
+
logger = options[:logger]
|
39
|
+
logger.debug "spork> parent PID = #{Process.pid}" if logger
|
40
|
+
|
41
|
+
child = fork do
|
42
|
+
begin
|
43
|
+
start = Time.now
|
44
|
+
logger.debug "spork> child PID = #{Process.pid}" if logger
|
45
|
+
|
46
|
+
# set the nice priority if needed
|
47
|
+
Process.setpriority(Process::PRIO_PROCESS, 0, options[:priority]) if options[:priority]
|
48
|
+
|
49
|
+
# disconnect from the rack
|
50
|
+
Spork.close_resources
|
51
|
+
|
52
|
+
# run the block of code that takes so long
|
53
|
+
yield
|
54
|
+
|
55
|
+
rescue => ex
|
56
|
+
#raise ex
|
57
|
+
logger.error "spork> Exception in child[#{Process.pid}] - #{ex.class}: #{ex.message}" if logger
|
58
|
+
ensure
|
59
|
+
logger.info "spork> child[#{Process.pid}] took #{Time.now - start} sec" if logger
|
60
|
+
# this form of exit doesn't call at_exit handlers
|
61
|
+
exit!(0)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# detach from child process (parent may still wait for detached process if they wish)
|
66
|
+
Process.detach(child) unless options[:no_detach]
|
67
|
+
|
68
|
+
return child
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
# Patch to work with passenger
|
74
|
+
if defined? Passenger::Rack::RequestHandler
|
75
|
+
class Passenger::Rack::RequestHandler
|
76
|
+
alias_method :orig_process_request, :process_request
|
77
|
+
def process_request(env, input, output)
|
78
|
+
Spork.resource_to_close(input)
|
79
|
+
Spork.resource_to_close(output)
|
80
|
+
orig_process_request(env, input, output)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/task.rb
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
$self_task=nil
|
2
|
+
|
3
|
+
module OpenTox
|
4
|
+
|
5
|
+
class Task
|
6
|
+
|
7
|
+
# due_to_time is only set in local tasks
|
8
|
+
TASK_ATTRIBS = [ :uri, :date, :title, :creator, :description, :hasStatus, :percentageCompleted, :resultURI, :due_to_time ]
|
9
|
+
TASK_ATTRIBS.each{ |a| attr_accessor(a) }
|
10
|
+
attr_accessor :http_code
|
11
|
+
|
12
|
+
private
|
13
|
+
def initialize(uri)
|
14
|
+
@uri = uri.to_s.strip
|
15
|
+
end
|
16
|
+
|
17
|
+
# create is private now, use OpenTox::Task.as_task
|
18
|
+
def self.create( params )
|
19
|
+
task_uri = RestClientWrapper.post(@@config[:services]["opentox-task"], params, nil, false).to_s
|
20
|
+
Task.find(task_uri.chomp)
|
21
|
+
end
|
22
|
+
|
23
|
+
public
|
24
|
+
def self.find( uri, accept_header=nil )
|
25
|
+
task = Task.new(uri)
|
26
|
+
task.reload( accept_header )
|
27
|
+
return task
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.from_data(data, content_type, code, base_uri)
|
31
|
+
task = Task.new(nil)
|
32
|
+
task.http_code = code
|
33
|
+
task.reload_from_data(data, content_type, base_uri)
|
34
|
+
return task
|
35
|
+
end
|
36
|
+
|
37
|
+
def reload( accept_header=nil )
|
38
|
+
unless accept_header
|
39
|
+
if (@@config[:yaml_hosts].include?(URI.parse(uri).host))
|
40
|
+
accept_header = "application/x-yaml"
|
41
|
+
else
|
42
|
+
accept_header = 'application/rdf+xml'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
result = RestClientWrapper.get(uri, {:accept => accept_header}, false)#'application/x-yaml'})
|
46
|
+
@http_code = result.code
|
47
|
+
reload_from_data(result, result.content_type, uri)
|
48
|
+
end
|
49
|
+
|
50
|
+
def reload_from_data( data, content_type, base_uri )
|
51
|
+
case content_type
|
52
|
+
when /yaml/
|
53
|
+
task = YAML.load data
|
54
|
+
TASK_ATTRIBS.each do |a|
|
55
|
+
raise "task yaml data invalid, key missing: "+a.to_s unless task.has_key?(a)
|
56
|
+
send("#{a.to_s}=".to_sym,task[a])
|
57
|
+
end
|
58
|
+
when /application\/rdf\+xml/
|
59
|
+
owl = OpenTox::Owl.from_data(data,base_uri,"Task")
|
60
|
+
self.uri = owl.uri
|
61
|
+
(TASK_ATTRIBS-[:uri]).each{|a| self.send("#{a.to_s}=".to_sym, owl.get(a.to_s))}
|
62
|
+
else
|
63
|
+
raise "content type for tasks not supported: "+content_type.to_s
|
64
|
+
end
|
65
|
+
raise "uri is null after loading" unless @uri and @uri.to_s.strip.size>0
|
66
|
+
end
|
67
|
+
|
68
|
+
def cancel
|
69
|
+
RestClientWrapper.put(File.join(@uri,'Cancelled'))
|
70
|
+
reload
|
71
|
+
end
|
72
|
+
|
73
|
+
def completed(uri)
|
74
|
+
RestClientWrapper.put(File.join(@uri,'Completed'),{:resultURI => uri})
|
75
|
+
reload
|
76
|
+
end
|
77
|
+
|
78
|
+
def error(description)
|
79
|
+
RestClientWrapper.put(File.join(@uri,'Error'),{:description => description.to_s[0..2000]})
|
80
|
+
reload
|
81
|
+
end
|
82
|
+
|
83
|
+
def pid=(pid)
|
84
|
+
RestClientWrapper.put(File.join(@uri,'pid'), {:pid => pid})
|
85
|
+
end
|
86
|
+
|
87
|
+
def running?
|
88
|
+
@hasStatus.to_s == 'Running'
|
89
|
+
end
|
90
|
+
|
91
|
+
def completed?
|
92
|
+
@hasStatus.to_s == 'Completed'
|
93
|
+
end
|
94
|
+
|
95
|
+
def error?
|
96
|
+
@hasStatus.to_s == 'Error'
|
97
|
+
end
|
98
|
+
|
99
|
+
# waits for a task, unless time exceeds or state is no longer running
|
100
|
+
def wait_for_completion(dur=0.3)
|
101
|
+
|
102
|
+
if (@uri.match(@@config[:services]["opentox-task"]))
|
103
|
+
due_to_time = (@due_to_time.is_a?(Time) ? @due_to_time : Time.parse(@due_to_time))
|
104
|
+
running_time = due_to_time - (@date.is_a?(Time) ? @date : Time.parse(@date))
|
105
|
+
else
|
106
|
+
# the date of the external task cannot be trusted, offest to local time might be to big
|
107
|
+
due_to_time = Time.new + EXTERNAL_TASK_MAX_DURATION
|
108
|
+
running_time = EXTERNAL_TASK_MAX_DURATION
|
109
|
+
end
|
110
|
+
LOGGER.debug "start waiting for task "+@uri.to_s+" at: "+Time.new.to_s+", waiting at least until "+due_to_time.to_s
|
111
|
+
|
112
|
+
while self.running?
|
113
|
+
sleep dur
|
114
|
+
reload
|
115
|
+
check_state
|
116
|
+
if (Time.new > due_to_time)
|
117
|
+
raise "max wait time exceeded ("+running_time.to_s+"sec), task: '"+@uri.to_s+"'"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
LOGGER.debug "Task '"+@hasStatus+"': "+@uri.to_s+", Result: "+@resultURI.to_s
|
122
|
+
end
|
123
|
+
|
124
|
+
def check_state
|
125
|
+
begin
|
126
|
+
raise "illegal task state, task is completed, resultURI is no URI: '"+@resultURI.to_s+
|
127
|
+
"'" unless @resultURI and Utils.is_uri?(@resultURI) if completed?
|
128
|
+
|
129
|
+
if @http_code == 202
|
130
|
+
raise "illegal task state, code is 202, but hasStatus is not Running: '"+@hasStatus+"'" unless running?
|
131
|
+
elsif @http_code == 201
|
132
|
+
raise "illegal task state, code is 201, but hasStatus is not Completed: '"+@hasStatus+"'" unless completed?
|
133
|
+
raise "illegal task state, code is 201, resultURI is no task-URI: '"+@resultURI.to_s+
|
134
|
+
"'" unless @resultURI and Utils.task_uri?(@resultURI)
|
135
|
+
end
|
136
|
+
rescue => ex
|
137
|
+
RestClientWrapper.raise_uri_error(ex.message, @uri)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# returns the task uri
|
142
|
+
# catches halts and exceptions, task state is set to error then
|
143
|
+
def self.as_task( title, creator, max_duration=DEFAULT_TASK_MAX_DURATION, description=nil )
|
144
|
+
#return yield nil
|
145
|
+
|
146
|
+
params = {:title=>title, :creator=>creator, :max_duration=>max_duration, :description=>description }
|
147
|
+
task = OpenTox::Task.create(params)
|
148
|
+
task_pid = Spork.spork(:logger => LOGGER) do
|
149
|
+
LOGGER.debug "Task #{task.uri} started #{Time.now}"
|
150
|
+
$self_task = task
|
151
|
+
|
152
|
+
begin
|
153
|
+
result = catch(:halt) do
|
154
|
+
yield task
|
155
|
+
end
|
156
|
+
# catching halt, set task state to error
|
157
|
+
if result && result.is_a?(Array) && result.size==2 && result[0]>202
|
158
|
+
LOGGER.error "task was halted: "+result.inspect
|
159
|
+
task.error(result[1])
|
160
|
+
return
|
161
|
+
end
|
162
|
+
LOGGER.debug "Task #{task.uri} done #{Time.now} -> "+result.to_s
|
163
|
+
task.completed(result)
|
164
|
+
rescue => ex
|
165
|
+
LOGGER.error "task failed: "+ex.message
|
166
|
+
LOGGER.error ": "+ex.backtrace.join("\n")
|
167
|
+
task.error(ex.message)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
task.pid = task_pid
|
171
|
+
LOGGER.debug "Started task: "+task.uri.to_s
|
172
|
+
task.uri
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# Example configuration for OpenTox, please adjust to your settings
|
2
|
+
#
|
3
|
+
# Database setup:
|
4
|
+
#
|
5
|
+
# Example MySql:
|
6
|
+
#
|
7
|
+
:database:
|
8
|
+
:adapter: mysql
|
9
|
+
:database: production
|
10
|
+
:username: root
|
11
|
+
:password: opentox
|
12
|
+
:host: localhost
|
13
|
+
#
|
14
|
+
# Example 1: Using external test services
|
15
|
+
#
|
16
|
+
# :services:
|
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/"
|
23
|
+
#
|
24
|
+
# Example 2: Using local services
|
25
|
+
:base_dir: /home/ist/webservices
|
26
|
+
:webserver: passenger
|
27
|
+
:services:
|
28
|
+
opentox-compound: "http://localhost/compound/"
|
29
|
+
opentox-dataset: "http://localhost/dataset/"
|
30
|
+
opentox-algorithm: "http://localhost/algorithm/"
|
31
|
+
opentox-model: "http://localhost/model/"
|
32
|
+
opentox-task: "http://localhost/task/"
|
33
|
+
opentox-validation: "http://localhost/validation/"
|
34
|
+
#
|
35
|
+
# Yaml capable hosts (faster than OWL-DL)
|
36
|
+
#
|
37
|
+
:yaml_hosts:
|
38
|
+
- "localhost"
|
39
|
+
|
40
|
+
# Uncomment for verbose logging
|
41
|
+
# :logger: debug
|