module SequenceServer
Top level module / namespace.
Define constants used by SequenceServer
module
Define Config
class.
Define Database
class.
This file defines all possible exceptions that can be thrown by SequenceServer
on startup.
Exceptions only ever inform another entity (downstream code or users) of an issue. Exceptions may or may not be recoverable.
Error classes should be seen as: the error code (class name), human readable message (to_s method), and necessary attributes to act on the error.
We define as many error classes as needed to be precise about the issue, thus making it easy for downstream code (bin/sequenceserver or config.ru) to act on them.
Define Sequence
class.
Define version number.
Constants
- DOTDIR
Constant for denoting the path ~/.sequenceserver
- Database
Captures a directory containing FASTA files and
BLAST
databases.Formatting a FASTA for use with BLAST+ will create 3 or 6 files, collectively referred to as a
BLAST
database.It is important that formatted
BLAST
database files have the same dirname and basename as the source FASTA forSequenceServer
to be able to tell formatted FASTA from unformatted. And that FASTA files be formatted with `parse_seqids` option of `makeblastdb` for sequence retrieval to work.SequenceServer
will always placeBLAST
database files alongside input FASTA, and use `parse_seqids` option of `makeblastdb` to format databases.- MINIMUM_BLAST_VERSION
Use a fixed minimum version of BLAST+
- Sequence
Provides simple sequence processing utilities via class methods. Instance of the class serves as a simple data object to captures sequences fetched from
BLAST
databases.NOTE:
What all do we need to consistently construct FASTA from `blastdbcmd's` output? It would seem rather straightforward. But it's not. FASTA format: >defline actual sequence where, defline = >id title ID of a sequence fetched from nr database should look like this: sequence id -> self.seqid ------------- accession -> self.accession ---------- gi|322796550|gb|EFZ19024.1| -> self.id --------- gi number -> self.gi while for local databases, the id should be the exact same, as in the original FASTA file: SI2.2.0_06267 -> self.id == self.seqid == self.accession.
- VERSION
Attributes
Public Class Methods
Rack-interface.
Inject our logger in the env and dispatch request to our controller.
# File lib/sequenceserver.rb, line 94 def call(env) env['rack.logger'] = logger Routes.call(env) end
# File lib/sequenceserver.rb, line 20 def environment ENV['RACK_ENV'] end
# File lib/sequenceserver.rb, line 36 def init(config = {}) @config = Config.new(config) init_binaries init_database load_extension check_num_threads self # We don't validate port and host settings. If SequenceServer is run # self-hosted, bind will fail on incorrect values. If SequenceServer # is run via Apache+Passenger, we don't need to worry. end
Run SequenceServer
interactively.
# File lib/sequenceserver.rb, line 100 def irb ARGV.clear require 'irb' IRB.setup nil IRB.conf[:MAIN_CONTEXT] = IRB::Irb.new.context require 'irb/ext/multi-irb' IRB.irb nil, self end
# File lib/sequenceserver.rb, line 32 def logger @logger ||= Logger.new(STDERR, verbose?) end
# File lib/sequenceserver.rb, line 69 def on_start puts '** SequenceServer is ready.' puts " Go to #{server_url} in your browser and start BLASTing!" puts ' Press CTRL+C to quit.' open_in_browser(server_url) end
# File lib/sequenceserver.rb, line 76 def on_stop puts puts '** Thank you for using SequenceServer :).' puts ' Please cite: ' puts ' Priyam A, Woodcroft BJ, Rai V, Moghul I, Munagala A, Ter F,' puts ' Chowdhary H, Pieniak I, Maynard LJ, Gibbins MA, Moon H,' puts ' Davis-Richardson A, Uludag M, Watson-Haigh N, Challis R,' puts ' Nakamura H, Favreau E, Gómez EA, Pluskal T, Leonard G,' puts ' Rumpf W & Wurm Y.' puts ' Sequenceserver: A modern graphical user interface for' puts ' custom BLAST databases.' puts ' Molecular Biology and Evolution (2019)' end
# File lib/sequenceserver.rb, line 28 def root File.dirname(File.dirname(__FILE__)) end
Run SequenceServer
as a self-hosted server using Thin webserver.
# File lib/sequenceserver.rb, line 53 def run check_host Server.run(self) rescue Errno::EADDRINUSE puts "** Could not bind to port #{config[:port]}." puts " Is SequenceServer already accessible at #{server_url}?" puts ' No? Try running SequenceServer on another port, like so:' puts puts ' sequenceserver -p 4570.' rescue Errno::EACCES puts "** Need root privilege to bind to port #{config[:port]}." puts ' It is not advisable to run SequenceServer as root.' puts ' Please use Apache/Nginx to bind to a privileged port.' puts ' Instructions available on http://sequenceserver.com.' end
# File lib/sequenceserver.rb, line 24 def verbose? @verbose ||= (environment == 'development') end
Private Class Methods
# File lib/sequenceserver.rb, line 185 def assert_blast_installed_and_compatible fail BLAST_NOT_INSTALLED unless command? 'blastdbcmd' version = `blastdbcmd -version`.split[1] fail BLAST_NOT_EXECUTABLE if !$CHILD_STATUS.success? || version.empty? if (parse_version(version) <=> parse_version(MINIMUM_BLAST_VERSION)) < 0 fail BLAST_NOT_COMPATIBLE, version end end
Check and warn user if host is 0.0.0.0 (default).
# File lib/sequenceserver.rb, line 156 def check_host # rubocop:disable Style/GuardClause if config[:host] == '0.0.0.0' logger.warn 'Will listen on all interfaces (0.0.0.0).' \ ' Consider using 127.0.0.1 (--host option).' end # rubocop:enable Style/GuardClause end
# File lib/sequenceserver.rb, line 143 def check_num_threads num_threads = Integer(config[:num_threads]) fail NUM_THREADS_INCORRECT unless num_threads > 0 logger.debug "Will use #{num_threads} threads to run BLAST." if num_threads > 256 logger.warn "Number of threads set at #{num_threads} is unusually high." end rescue raise NUM_THREADS_INCORRECT end
Return `true` if the given command exists and is executable.
# File lib/sequenceserver.rb, line 218 def command?(command) system("which #{command} > /dev/null 2>&1") end
Export NCBI BLAST+ bin dir to PATH environment variable.
# File lib/sequenceserver.rb, line 178 def export_bin_dir bin_dir = config[:bin] return unless bin_dir return if ENV['PATH'].split(':').include? bin_dir ENV['PATH'] = "#{bin_dir}:#{ENV['PATH']}" end
# File lib/sequenceserver.rb, line 111 def init_binaries if config[:bin] config[:bin] = File.expand_path config[:bin] unless File.exist?(config[:bin]) && File.directory?(config[:bin]) fail BIN_DIR_NOT_FOUND, config[:bin] end logger.debug("Will use NCBI BLAST+ at: #{config[:bin]}") export_bin_dir else logger.debug('Will use NCBI BLAST+ at: $PATH') end assert_blast_installed_and_compatible end
# File lib/sequenceserver.rb, line 126 def init_database fail DATABASE_DIR_NOT_SET unless config[:database_dir] config[:database_dir] = File.expand_path(config[:database_dir]) unless File.exist?(config[:database_dir]) && File.directory?(config[:database_dir]) fail DATABASE_DIR_NOT_FOUND, config[:database_dir] end logger.debug("Will use BLAST+ databases at: #{config[:database_dir]}") Database.scan_databases_dir Database.each do |database| logger.debug("Found #{database.type} database '#{database.title}'" \ " at '#{database.name}'") end end
# File lib/sequenceserver.rb, line 165 def load_extension return unless config[:require] config[:require] = File.expand_path config[:require] unless File.exist?(config[:require]) && File.file?(config[:require]) fail EXTENSION_FILE_NOT_FOUND, config[:require] end logger.debug("Loading extension: #{config[:require]}") require config[:require] end
# File lib/sequenceserver.rb, line 200 def open_in_browser(server_url) return if using_ssh? || verbose? if RUBY_PLATFORM =~ /linux/ && xdg? system "xdg-open #{server_url}" elsif RUBY_PLATFORM =~ /darwin/ system "open #{server_url}" end end
# File lib/sequenceserver.rb, line 222 def parse_version(version_string) version_string.split('.').map(&:to_i) end
# File lib/sequenceserver.rb, line 194 def server_url host = config[:host] host = 'localhost' if host == '127.0.0.1' || host == '0.0.0.0' "http://#{host}:#{config[:port]}" end
# File lib/sequenceserver.rb, line 209 def using_ssh? true if ENV['SSH_CLIENT'] || ENV['SSH_TTY'] || ENV['SSH_CONNECTION'] end
# File lib/sequenceserver.rb, line 213 def xdg? true if ENV['DISPLAY'] && command?('xdg-open') end