1# Copyright 2001-2004 Brad Chapman. 2# Revisions copyright 2009-2013 by Peter Cock. 3# All rights reserved. 4# 5# This file is part of the Biopython distribution and governed by your 6# choice of the "Biopython License Agreement" or the "BSD 3-Clause License". 7# Please see the LICENSE file that should have been included as part of this 8# package. 9"""General mechanisms to access applications in Biopython (OBSOLETE). 10 11This module is not intended for direct use. It provides the basic objects which 12are subclassed by our command line wrappers, such as: 13 14 - Bio.Align.Applications 15 - Bio.Blast.Applications 16 - Bio.Emboss.Applications 17 - Bio.Sequencing.Applications 18 19These modules provide wrapper classes for command line tools to help you 20construct command line strings by setting the values of each parameter. 21The finished command line strings are then normally invoked via the built-in 22Python module subprocess. 23 24Due to the on going maintainance burden or keeping command line application 25wrappers up to date, we have decided to deprecate and eventually remove them. 26We instead now recommend building your command line and invoking it directly 27with the subprocess module. 28""" 29import os 30import platform 31import sys 32import subprocess 33import re 34 35 36# Use this regular expression to test the property names are going to 37# be valid as Python properties or arguments 38_re_prop_name = re.compile(r"^[a-zA-Z][a-zA-Z0-9_]*$") 39assert _re_prop_name.match("t") 40assert _re_prop_name.match("test") 41assert _re_prop_name.match("_test") is None # we don't want private names 42assert _re_prop_name.match("-test") is None 43assert _re_prop_name.match("any-hyphen") is None 44assert _re_prop_name.match("underscore_ok") 45assert _re_prop_name.match("test_name") 46assert _re_prop_name.match("test2") 47# These are reserved names in Python itself, 48_reserved_names = [ 49 "and", 50 "del", 51 "from", 52 "not", 53 "while", 54 "as", 55 "elif", 56 "global", 57 "or", 58 "with", 59 "assert", 60 "else", 61 "if", 62 "pass", 63 "yield", 64 "break", 65 "except", 66 "import", 67 "print", 68 "class", 69 "exec", 70 "in", 71 "raise", 72 "continue", 73 "finally", 74 "is", 75 "return", 76 "def", 77 "for", 78 "lambda", 79 "try", 80] 81# These are reserved names due to the way the wrappers work 82_local_reserved_names = ["set_parameter"] 83 84 85class ApplicationError(subprocess.CalledProcessError): 86 """Raised when an application returns a non-zero exit status (OBSOLETE). 87 88 The exit status will be stored in the returncode attribute, similarly 89 the command line string used in the cmd attribute, and (if captured) 90 stdout and stderr as strings. 91 92 This exception is a subclass of subprocess.CalledProcessError. 93 94 >>> err = ApplicationError(-11, "helloworld", "", "Some error text") 95 >>> err.returncode, err.cmd, err.stdout, err.stderr 96 (-11, 'helloworld', '', 'Some error text') 97 >>> print(err) 98 Non-zero return code -11 from 'helloworld', message 'Some error text' 99 100 """ 101 102 def __init__(self, returncode, cmd, stdout="", stderr=""): 103 """Initialize the class.""" 104 self.returncode = returncode 105 self.cmd = cmd 106 self.stdout = stdout 107 self.stderr = stderr 108 109 def __str__(self): 110 """Format the error as a string.""" 111 # get first line of any stderr message 112 try: 113 msg = self.stderr.lstrip().split("\n", 1)[0].rstrip() 114 except Exception: # TODO, ValueError? AttributeError? 115 msg = "" 116 if msg: 117 return "Non-zero return code %d from %r, message %r" % ( 118 self.returncode, 119 self.cmd, 120 msg, 121 ) 122 else: 123 return "Non-zero return code %d from %r" % (self.returncode, self.cmd) 124 125 def __repr__(self): 126 """Represent the error as a string.""" 127 return "ApplicationError(%i, %s, %s, %s)" % ( 128 self.returncode, 129 self.cmd, 130 self.stdout, 131 self.stderr, 132 ) 133 134 135class AbstractCommandline: 136 r"""Generic interface for constructing command line strings (OBSOLETE). 137 138 This class shouldn't be called directly; it should be subclassed to 139 provide an implementation for a specific application. 140 141 For a usage example we'll show one of the EMBOSS wrappers. You can set 142 options when creating the wrapper object using keyword arguments - or 143 later using their corresponding properties: 144 145 >>> from Bio.Emboss.Applications import WaterCommandline 146 >>> cline = WaterCommandline(gapopen=10, gapextend=0.5) 147 >>> cline 148 WaterCommandline(cmd='water', gapopen=10, gapextend=0.5) 149 150 You can instead manipulate the parameters via their properties, e.g. 151 152 >>> cline.gapopen 153 10 154 >>> cline.gapopen = 20 155 >>> cline 156 WaterCommandline(cmd='water', gapopen=20, gapextend=0.5) 157 158 You can clear a parameter you have already added by 'deleting' the 159 corresponding property: 160 161 >>> del cline.gapopen 162 >>> cline.gapopen 163 >>> cline 164 WaterCommandline(cmd='water', gapextend=0.5) 165 166 Once you have set the parameters you need, you can turn the object into 167 a string (e.g. to log the command): 168 169 >>> str(cline) 170 Traceback (most recent call last): 171 ... 172 ValueError: You must either set outfile (output filename), or enable filter or stdout (output to stdout). 173 174 In this case the wrapper knows certain arguments are required to construct 175 a valid command line for the tool. For a complete example, 176 177 >>> from Bio.Emboss.Applications import WaterCommandline 178 >>> water_cmd = WaterCommandline(gapopen=10, gapextend=0.5) 179 >>> water_cmd.asequence = "asis:ACCCGGGCGCGGT" 180 >>> water_cmd.bsequence = "asis:ACCCGAGCGCGGT" 181 >>> water_cmd.outfile = "temp_water.txt" 182 >>> print(water_cmd) 183 water -outfile=temp_water.txt -asequence=asis:ACCCGGGCGCGGT -bsequence=asis:ACCCGAGCGCGGT -gapopen=10 -gapextend=0.5 184 >>> water_cmd 185 WaterCommandline(cmd='water', outfile='temp_water.txt', asequence='asis:ACCCGGGCGCGGT', bsequence='asis:ACCCGAGCGCGGT', gapopen=10, gapextend=0.5) 186 187 You would typically run the command line via a standard Python operating 188 system call using the subprocess module for full control. For the simple 189 case where you just want to run the command and get the output: 190 191 stdout, stderr = water_cmd() 192 193 Note that by default we assume the underlying tool is installed on the 194 system $PATH environment variable. This is normal under Linux/Unix, but 195 may need to be done manually under Windows. Alternatively, you can specify 196 the full path to the binary as the first argument (cmd): 197 198 >>> from Bio.Emboss.Applications import WaterCommandline 199 >>> water_cmd = WaterCommandline(r"C:\Program Files\EMBOSS\water.exe", 200 ... gapopen=10, gapextend=0.5, 201 ... asequence="asis:ACCCGGGCGCGGT", 202 ... bsequence="asis:ACCCGAGCGCGGT", 203 ... outfile="temp_water.txt") 204 >>> print(water_cmd) 205 "C:\Program Files\EMBOSS\water.exe" -outfile=temp_water.txt -asequence=asis:ACCCGGGCGCGGT -bsequence=asis:ACCCGAGCGCGGT -gapopen=10 -gapextend=0.5 206 207 Notice that since the path name includes a space it has automatically 208 been quoted. 209 210 """ 211 212 # TODO - Replace the above example since EMBOSS doesn't work properly 213 # if installed into a folder with a space like "C:\Program Files\EMBOSS" 214 # 215 # Note the call example above is not a doctest as we can't handle EMBOSS 216 # (or any other tool) being missing in the unit tests. 217 218 parameters = None # will be a list defined in subclasses 219 220 def __init__(self, cmd, **kwargs): 221 """Create a new instance of a command line wrapper object.""" 222 # Init method - should be subclassed! 223 # 224 # The subclass methods should look like this: 225 # 226 # def __init__(self, cmd="muscle", **kwargs): 227 # self.parameters = [...] 228 # AbstractCommandline.__init__(self, cmd, **kwargs) 229 # 230 # i.e. There should have an optional argument "cmd" to set the location 231 # of the executable (with a sensible default which should work if the 232 # command is on the path on Unix), and keyword arguments. It should 233 # then define a list of parameters, all objects derived from the base 234 # class _AbstractParameter. 235 # 236 # The keyword arguments should be any valid parameter name, and will 237 # be used to set the associated parameter. 238 self.program_name = cmd 239 try: 240 parameters = self.parameters 241 except AttributeError: 242 raise AttributeError( 243 "Subclass should have defined self.parameters" 244 ) from None 245 # Create properties for each parameter at run time 246 aliases = set() 247 for p in parameters: 248 if not p.names: 249 if not isinstance(p, _StaticArgument): 250 raise TypeError("Expected %r to be of type _StaticArgument" % p) 251 continue 252 for name in p.names: 253 if name in aliases: 254 raise ValueError("Parameter alias %s multiply defined" % name) 255 aliases.add(name) 256 name = p.names[-1] 257 if _re_prop_name.match(name) is None: 258 raise ValueError( 259 "Final parameter name %r cannot be used as " 260 "an argument or property name in python" % name 261 ) 262 if name in _reserved_names: 263 raise ValueError( 264 "Final parameter name %r cannot be used as " 265 "an argument or property name because it is " 266 "a reserved word in python" % name 267 ) 268 if name in _local_reserved_names: 269 raise ValueError( 270 "Final parameter name %r cannot be used as " 271 "an argument or property name due to the " 272 "way the AbstractCommandline class works" % name 273 ) 274 275 # Beware of binding-versus-assignment confusion issues 276 def getter(name): 277 return lambda x: x._get_parameter(name) 278 279 def setter(name): 280 return lambda x, value: x.set_parameter(name, value) 281 282 def deleter(name): 283 return lambda x: x._clear_parameter(name) 284 285 doc = p.description 286 if isinstance(p, _Switch): 287 doc += ( 288 "\n\nThis property controls the addition of the %s " 289 "switch, treat this property as a boolean." % p.names[0] 290 ) 291 else: 292 doc += ( 293 "\n\nThis controls the addition of the %s parameter " 294 "and its associated value. Set this property to the " 295 "argument value required." % p.names[0] 296 ) 297 prop = property(getter(name), setter(name), deleter(name), doc) 298 setattr(self.__class__, name, prop) # magic! 299 for key, value in kwargs.items(): 300 self.set_parameter(key, value) 301 302 def _validate(self): 303 """Make sure the required parameters have been set (PRIVATE). 304 305 No return value - it either works or raises a ValueError. 306 307 This is a separate method (called from __str__) so that subclasses may 308 override it. 309 """ 310 for p in self.parameters: 311 # Check for missing required parameters: 312 if p.is_required and not (p.is_set): 313 raise ValueError("Parameter %s is not set." % p.names[-1]) 314 # Also repeat the parameter validation here, just in case? 315 316 def __str__(self): 317 """Make the commandline string with the currently set options. 318 319 e.g. 320 321 >>> from Bio.Emboss.Applications import WaterCommandline 322 >>> cline = WaterCommandline(gapopen=10, gapextend=0.5) 323 >>> cline.asequence = "asis:ACCCGGGCGCGGT" 324 >>> cline.bsequence = "asis:ACCCGAGCGCGGT" 325 >>> cline.outfile = "temp_water.txt" 326 >>> print(cline) 327 water -outfile=temp_water.txt -asequence=asis:ACCCGGGCGCGGT -bsequence=asis:ACCCGAGCGCGGT -gapopen=10 -gapextend=0.5 328 >>> str(cline) 329 'water -outfile=temp_water.txt -asequence=asis:ACCCGGGCGCGGT -bsequence=asis:ACCCGAGCGCGGT -gapopen=10 -gapextend=0.5' 330 """ 331 self._validate() 332 commandline = "%s " % _escape_filename(self.program_name) 333 for parameter in self.parameters: 334 if parameter.is_set: 335 # This will include a trailing space: 336 commandline += str(parameter) 337 return commandline.strip() # remove trailing space 338 339 def __repr__(self): 340 """Return a representation of the command line object for debugging. 341 342 e.g. 343 344 >>> from Bio.Emboss.Applications import WaterCommandline 345 >>> cline = WaterCommandline(gapopen=10, gapextend=0.5) 346 >>> cline.asequence = "asis:ACCCGGGCGCGGT" 347 >>> cline.bsequence = "asis:ACCCGAGCGCGGT" 348 >>> cline.outfile = "temp_water.txt" 349 >>> print(cline) 350 water -outfile=temp_water.txt -asequence=asis:ACCCGGGCGCGGT -bsequence=asis:ACCCGAGCGCGGT -gapopen=10 -gapextend=0.5 351 >>> cline 352 WaterCommandline(cmd='water', outfile='temp_water.txt', asequence='asis:ACCCGGGCGCGGT', bsequence='asis:ACCCGAGCGCGGT', gapopen=10, gapextend=0.5) 353 """ 354 answer = "%s(cmd=%r" % (self.__class__.__name__, self.program_name) 355 for parameter in self.parameters: 356 if parameter.is_set: 357 if isinstance(parameter, _Switch): 358 answer += ", %s=True" % parameter.names[-1] 359 else: 360 answer += ", %s=%r" % (parameter.names[-1], parameter.value) 361 answer += ")" 362 return answer 363 364 def _get_parameter(self, name): 365 """Get a commandline option value (PRIVATE).""" 366 for parameter in self.parameters: 367 if name in parameter.names: 368 if isinstance(parameter, _Switch): 369 return parameter.is_set 370 else: 371 return parameter.value 372 raise ValueError("Option name %s was not found." % name) 373 374 def _clear_parameter(self, name): 375 """Reset or clear a commandline option value (PRIVATE).""" 376 cleared_option = False 377 for parameter in self.parameters: 378 if name in parameter.names: 379 parameter.value = None 380 parameter.is_set = False 381 cleared_option = True 382 if not cleared_option: 383 raise ValueError("Option name %s was not found." % name) 384 385 def set_parameter(self, name, value=None): 386 """Set a commandline option for a program (OBSOLETE). 387 388 Every parameter is available via a property and as a named 389 keyword when creating the instance. Using either of these is 390 preferred to this legacy set_parameter method which is now 391 OBSOLETE, and likely to be DEPRECATED and later REMOVED in 392 future releases. 393 """ 394 set_option = False 395 for parameter in self.parameters: 396 if name in parameter.names: 397 if isinstance(parameter, _Switch): 398 if value is None: 399 import warnings 400 401 warnings.warn( 402 "For a switch type argument like %s, " 403 "we expect a boolean. None is treated " 404 "as FALSE!" % parameter.names[-1] 405 ) 406 parameter.is_set = bool(value) 407 set_option = True 408 else: 409 if value is not None: 410 self._check_value(value, name, parameter.checker_function) 411 parameter.value = value 412 parameter.is_set = True 413 set_option = True 414 if not set_option: 415 raise ValueError("Option name %s was not found." % name) 416 417 def _check_value(self, value, name, check_function): 418 """Check whether the given value is valid (PRIVATE). 419 420 No return value - it either works or raises a ValueError. 421 422 This uses the passed function 'check_function', which can either 423 return a [0, 1] (bad, good) value or raise an error. Either way 424 this function will raise an error if the value is not valid, or 425 finish silently otherwise. 426 """ 427 if check_function is not None: 428 is_good = check_function(value) # May raise an exception 429 if is_good not in [0, 1, True, False]: 430 raise ValueError( 431 "Result of check_function: %r is of an unexpected value" % is_good 432 ) 433 if not is_good: 434 raise ValueError( 435 "Invalid parameter value %r for parameter %s" % (value, name) 436 ) 437 438 def __setattr__(self, name, value): 439 """Set attribute name to value (PRIVATE). 440 441 This code implements a workaround for a user interface issue. 442 Without this __setattr__ attribute-based assignment of parameters 443 will silently accept invalid parameters, leading to known instances 444 of the user assuming that parameters for the application are set, 445 when they are not. 446 447 >>> from Bio.Emboss.Applications import WaterCommandline 448 >>> cline = WaterCommandline(gapopen=10, gapextend=0.5, stdout=True) 449 >>> cline.asequence = "a.fasta" 450 >>> cline.bsequence = "b.fasta" 451 >>> cline.csequence = "c.fasta" 452 Traceback (most recent call last): 453 ... 454 ValueError: Option name csequence was not found. 455 >>> print(cline) 456 water -stdout -asequence=a.fasta -bsequence=b.fasta -gapopen=10 -gapextend=0.5 457 458 This workaround uses a whitelist of object attributes, and sets the 459 object attribute list as normal, for these. Other attributes are 460 assumed to be parameters, and passed to the self.set_parameter method 461 for validation and assignment. 462 """ 463 if name in ["parameters", "program_name"]: # Allowed attributes 464 self.__dict__[name] = value 465 else: 466 self.set_parameter(name, value) # treat as a parameter 467 468 def __call__(self, stdin=None, stdout=True, stderr=True, cwd=None, env=None): 469 """Execute command, wait for it to finish, return (stdout, stderr). 470 471 Runs the command line tool and waits for it to finish. If it returns 472 a non-zero error level, an exception is raised. Otherwise two strings 473 are returned containing stdout and stderr. 474 475 The optional stdin argument should be a string of data which will be 476 passed to the tool as standard input. 477 478 The optional stdout and stderr argument may be filenames (string), 479 but otherwise are treated as a booleans, and control if the output 480 should be captured as strings (True, default), or ignored by sending 481 it to /dev/null to avoid wasting memory (False). If sent to a file 482 or ignored, then empty string(s) are returned. 483 484 The optional cwd argument is a string giving the working directory 485 to run the command from. See Python's subprocess module documentation 486 for more details. 487 488 The optional env argument is a dictionary setting the environment 489 variables to be used in the new process. By default the current 490 process' environment variables are used. See Python's subprocess 491 module documentation for more details. 492 493 Default example usage:: 494 495 from Bio.Emboss.Applications import WaterCommandline 496 water_cmd = WaterCommandline(gapopen=10, gapextend=0.5, 497 stdout=True, auto=True, 498 asequence="a.fasta", bsequence="b.fasta") 499 print("About to run: %s" % water_cmd) 500 std_output, err_output = water_cmd() 501 502 This functionality is similar to subprocess.check_output(). In general 503 if you require more control over running the command, use subprocess 504 directly. 505 506 When the program called returns a non-zero error level, a custom 507 ApplicationError exception is raised. This includes any stdout and 508 stderr strings captured as attributes of the exception object, since 509 they may be useful for diagnosing what went wrong. 510 """ 511 if not stdout: 512 stdout_arg = open(os.devnull, "w") 513 elif isinstance(stdout, str): 514 stdout_arg = open(stdout, "w") 515 else: 516 stdout_arg = subprocess.PIPE 517 518 if not stderr: 519 stderr_arg = open(os.devnull, "w") 520 elif isinstance(stderr, str): 521 if stdout == stderr: 522 stderr_arg = stdout_arg # Write both to the same file 523 else: 524 stderr_arg = open(stderr, "w") 525 else: 526 stderr_arg = subprocess.PIPE 527 528 # We may not need to supply any piped input, but we setup the 529 # standard input pipe anyway as a work around for a python 530 # bug if this is called from a Windows GUI program. For 531 # details, see http://bugs.python.org/issue1124861 532 # 533 # Using universal newlines is important on Python 3, this 534 # gives unicode handles rather than bytes handles. 535 536 # Windows 7, 8, 8.1 and 10 want shell = True 537 if sys.platform != "win32": 538 use_shell = True 539 else: 540 win_ver = platform.win32_ver()[0] 541 if win_ver in ["7", "8", "post2012Server", "10"]: 542 use_shell = True 543 else: 544 use_shell = False 545 child_process = subprocess.Popen( 546 str(self), 547 stdin=subprocess.PIPE, 548 stdout=stdout_arg, 549 stderr=stderr_arg, 550 universal_newlines=True, 551 cwd=cwd, 552 env=env, 553 shell=use_shell, 554 ) 555 # Use .communicate as can get deadlocks with .wait(), see Bug 2804 556 stdout_str, stderr_str = child_process.communicate(stdin) 557 if not stdout: 558 assert not stdout_str, stdout_str 559 if not stderr: 560 assert not stderr_str, stderr_str 561 return_code = child_process.returncode 562 563 # Particularly important to close handles on Jython and PyPy 564 # (where garbage collection is less predictable) and on Windows 565 # (where cannot delete files with an open handle): 566 if not stdout or isinstance(stdout, str): 567 # We opened /dev/null or a file 568 stdout_arg.close() 569 if not stderr or (isinstance(stderr, str) and stdout != stderr): 570 # We opened /dev/null or a file 571 stderr_arg.close() 572 573 if return_code: 574 raise ApplicationError(return_code, str(self), stdout_str, stderr_str) 575 return stdout_str, stderr_str 576 577 578class _AbstractParameter: 579 """A class to hold information about a parameter for a commandline. 580 581 Do not use this directly, instead use one of the subclasses. 582 """ 583 584 def __init__(self): 585 raise NotImplementedError 586 587 def __str__(self): 588 raise NotImplementedError 589 590 591class _Option(_AbstractParameter): 592 """Represent an option that can be set for a program. 593 594 This holds UNIXish options like --append=yes and -a yes, 595 where a value (here "yes") is generally expected. 596 597 For UNIXish options like -kimura in clustalw which don't 598 take a value, use the _Switch object instead. 599 600 Attributes: 601 - names -- a list of string names (typically two entries) by which 602 the parameter can be set via the legacy set_parameter method 603 (eg ["-a", "--append", "append"]). The first name in list is used 604 when building the command line. The last name in the list is a 605 "human readable" name describing the option in one word. This 606 must be a valid Python identifier as it is used as the property 607 name and as a keyword argument, and should therefore follow PEP8 608 naming. 609 - description -- a description of the option. This is used as 610 the property docstring. 611 - filename -- True if this argument is a filename (or other argument 612 that should be quoted) and should be automatically quoted if it 613 contains spaces. 614 - checker_function -- a reference to a function that will determine 615 if a given value is valid for this parameter. This function can either 616 raise an error when given a bad value, or return a [0, 1] decision on 617 whether the value is correct. 618 - equate -- should an equals sign be inserted if a value is used? 619 - is_required -- a flag to indicate if the parameter must be set for 620 the program to be run. 621 - is_set -- if the parameter has been set 622 - value -- the value of a parameter 623 624 """ 625 626 def __init__( 627 self, 628 names, 629 description, 630 filename=False, 631 checker_function=None, 632 is_required=False, 633 equate=True, 634 ): 635 self.names = names 636 if not isinstance(description, str): 637 raise TypeError("Should be a string: %r for %s" % (description, names[-1])) 638 # Note 'filename' is for any string with spaces that needs quoting 639 self.is_filename = filename 640 self.checker_function = checker_function 641 self.description = description 642 self.equate = equate 643 self.is_required = is_required 644 645 self.is_set = False 646 self.value = None 647 648 def __str__(self): 649 """Return the value of this option for the commandline. 650 651 Includes a trailing space. 652 """ 653 # Note: Before equate was handled explicitly, the old 654 # code would do either "--name " or "--name=value ", 655 # or " -name " or " -name value ". This choice is now 656 # now made explicitly when setting up the option. 657 if self.value is None: 658 return "%s " % self.names[0] 659 if self.is_filename: 660 v = _escape_filename(self.value) 661 else: 662 v = str(self.value) 663 if self.equate: 664 return "%s=%s " % (self.names[0], v) 665 else: 666 return "%s %s " % (self.names[0], v) 667 668 669class _Switch(_AbstractParameter): 670 """Represent an optional argument switch for a program. 671 672 This holds UNIXish options like -kimura in clustalw which don't 673 take a value, they are either included in the command string 674 or omitted. 675 676 Attributes: 677 - names -- a list of string names (typically two entries) by which 678 the parameter can be set via the legacy set_parameter method 679 (eg ["-a", "--append", "append"]). The first name in list is used 680 when building the command line. The last name in the list is a 681 "human readable" name describing the option in one word. This 682 must be a valid Python identifier as it is used as the property 683 name and as a keyword argument, and should therefore follow PEP8 684 naming. 685 - description -- a description of the option. This is used as 686 the property docstring. 687 - is_set -- if the parameter has been set 688 689 NOTE - There is no value attribute, see is_set instead, 690 691 """ 692 693 def __init__(self, names, description): 694 self.names = names 695 self.description = description 696 self.is_set = False 697 self.is_required = False 698 699 def __str__(self): 700 """Return the value of this option for the commandline. 701 702 Includes a trailing space. 703 """ 704 assert not hasattr(self, "value") 705 if self.is_set: 706 return "%s " % self.names[0] 707 else: 708 return "" 709 710 711class _Argument(_AbstractParameter): 712 """Represent an argument on a commandline. 713 714 The names argument should be a list containing one string. 715 This must be a valid Python identifier as it is used as the 716 property name and as a keyword argument, and should therefore 717 follow PEP8 naming. 718 """ 719 720 def __init__( 721 self, 722 names, 723 description, 724 filename=False, 725 checker_function=None, 726 is_required=False, 727 ): 728 # if len(names) != 1: 729 # raise ValueError("The names argument to _Argument should be a " 730 # "single entry list with a PEP8 property name.") 731 self.names = names 732 if not isinstance(description, str): 733 raise TypeError("Should be a string: %r for %s" % (description, names[-1])) 734 # Note 'filename' is for any string with spaces that needs quoting 735 self.is_filename = filename 736 self.checker_function = checker_function 737 self.description = description 738 self.is_required = is_required 739 self.is_set = False 740 self.value = None 741 742 def __str__(self): 743 if self.value is None: 744 return " " 745 elif self.is_filename: 746 return "%s " % _escape_filename(self.value) 747 else: 748 return "%s " % self.value 749 750 751class _ArgumentList(_Argument): 752 """Represent a variable list of arguments on a command line, e.g. multiple filenames.""" 753 754 # TODO - Option to require at least one value? e.g. min/max count? 755 756 def __str__(self): 757 if not isinstance(self.value, list): 758 raise TypeError("Arguments should be a list") 759 if not self.value: 760 raise ValueError("Requires at least one filename") 761 # A trailing space is required so that parameters following the last filename 762 # do not appear merged. 763 # e.g.: samtools cat in1.bam in2.bam-o out.sam [without trailing space][Incorrect] 764 # samtools cat in1.bam in2.bam -o out.sam [with trailing space][Correct] 765 if self.is_filename: 766 return " ".join(_escape_filename(v) for v in self.value) + " " 767 else: 768 return " ".join(self.value) + " " 769 770 771class _StaticArgument(_AbstractParameter): 772 """Represent a static (read only) argument on a commandline. 773 774 This is not intended to be exposed as a named argument or 775 property of a command line wrapper object. 776 """ 777 778 def __init__(self, value): 779 self.names = [] 780 self.is_required = False 781 self.is_set = True 782 self.value = value 783 784 def __str__(self): 785 return "%s " % self.value 786 787 788def _escape_filename(filename): 789 """Escape filenames with spaces by adding quotes (PRIVATE). 790 791 Note this will not add quotes if they are already included: 792 793 >>> print((_escape_filename('example with spaces'))) 794 "example with spaces" 795 >>> print((_escape_filename('"example with spaces"'))) 796 "example with spaces" 797 >>> print((_escape_filename(1))) 798 1 799 800 Note the function is more generic than the name suggests, since it 801 is used to add quotes around any string arguments containing spaces. 802 """ 803 # Is adding the following helpful 804 # if os.path.isfile(filename): 805 # # On Windows, if the file exists, we can ask for 806 # # its alternative short name (DOS style 8.3 format) 807 # # which has no spaces in it. Note that this name 808 # # is not portable between machines, or even folder! 809 # try: 810 # import win32api 811 # short = win32api.GetShortPathName(filename) 812 # assert os.path.isfile(short) 813 # return short 814 # except ImportError: 815 # pass 816 if not isinstance(filename, str): 817 # for example the NCBI BLAST+ -outfmt argument can be an integer 818 return filename 819 if " " not in filename: 820 return filename 821 # We'll just quote it - works on Windows, Mac OS X etc 822 if filename.startswith('"') and filename.endswith('"'): 823 # Its already quoted 824 return filename 825 else: 826 return '"%s"' % filename 827 828 829def _test(): 830 """Run the Bio.Application module's doctests (PRIVATE).""" 831 import doctest 832 833 doctest.testmod(verbose=1) 834 835 836if __name__ == "__main__": 837 # Run the doctests 838 _test() 839