This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Provide a 'print' action for argparse
Type: enhancement Stage: resolved
Components: Library (Lib) Versions: Python 3.5
process
Status: closed Resolution: out of date
Dependencies: Superseder:
Assigned To: rhettinger Nosy List: berker.peksag, bethard, denilsonsa, eric.araujo, georg.brandl, paul.j3, rhettinger, travistouchdown
Priority: normal Keywords: patch

Created on 2010-07-28 15:10 by travistouchdown, last changed 2022-04-11 14:57 by admin. This issue is now closed.

Files
File name Uploaded Description Edit
argparse.diff travistouchdown, 2010-07-30 13:32 patch adding a 'write' action to argparse
argparse.diff travistouchdown, 2010-12-22 15:47 review
try_write.py paul.j3, 2014-07-19 22:17
Messages (15)
msg111824 - (view) Author: Dennis Malcorps (travistouchdown) Date: 2010-07-28 15:10
Currently argparse has a 'version' action which can be triggered by user defined options which prints out a custom string.

parser.add_argument("--version", action="version", version="test 1.2.3")

Since the 'version' action can be added multiple times, it can be used to output different kinds of information, like the program's license.

parser.add_argument("--license", action="version", version="This file is licensed under GPL.... [a huge amount of text]")

The only drawback is that linebreaks are substituted with a normal space. So I propose a 'print' action (perhaps as a replacement for 'version'?) which respects whitespace characters.

parser.add_argument("--version", action="print", message="test 1.2.3")
parser.add_argument("--license", action="print", message="This file is licensed under GPL.... [a huge amount of text, now properly formatted!]")
parser.add_argument("--insult-me", action="print", message="You sick *peep* , *peep* yourself in *peep*")

Currently, the only solution is to create a custom action which is IMHO a bit overkill for just printing a simple string to stdout.
msg111924 - (view) Author: Georg Brandl (georg.brandl) * (Python committer) Date: 2010-07-29 13:47
Sounds like a good addition to me.
msg111936 - (view) Author: Steven Bethard (bethard) * (Python committer) Date: 2010-07-29 14:26
Should this print to stdout or stderr? I wonder if the API should allow either, and instead look like:

parser.add_argument('--license', action='write', message='...', file=sys.stdout)

Where sys.stdout would be the default for the file= argument. The action would then just literally call file.write(message), so the behavior would be pretty easy to explain.

Of course, at that point it makes me wonder if maybe it wouldn't just be better to have an easy way to have some arbitrary function called without having to subclass Action, e.g.:

parser.add_argument('--license', action='call', callable=lambda: sys.stdout.write(message))

Basically this would be a shorthand for subclassing Action when you don't need any information about the command line.
msg111949 - (view) Author: Dennis Malcorps (travistouchdown) Date: 2010-07-29 15:22
> parser.add_argument('--license', action='write', message='...', file=sys.stdout)
The ability to redirect the output would be a nice addition.

> parser.add_argument('--license', action='call', callable=lambda: sys.stdout.write(message)) 
optparse already has a 'callable' action, I had the impression it was left out of argparse on purpose, even tough I couldn't imagine why...

Either way is fine for me.
msg112042 - (view) Author: Steven Bethard (bethard) * (Python committer) Date: 2010-07-30 06:54
The equivalent to optparse callback is basically to define __call__ in a subclass of Action. Pretty much the same amount of work because they both have complicated parameters.

The "callable" I (not fully confidently) proposed would just be a shorthand for defining __call__ in a subclass of Action when you don't care about any of the other command line info.

I guess, without further use cases for "callable" and given that you can subclass Action for other use cases, let's just do the action='write' version. Feel free to supply a patch, or I will when I get some time for argparse again.
msg112076 - (view) Author: Dennis Malcorps (travistouchdown) Date: 2010-07-30 13:32
Here is a patch that adds a 'write' action to argparse. Usage example:

>>> parser = argparse.ArgumentParser()
>>> parser.add_argument("--license", action="write", message="This file\nis licensed under \t GPL")
>>> parser.parse_args(['--license'])
This file
is licensed under        GPL

A linebreak will be added after the message. The Output can be redirected with the optional 'file=' argument (it defaults to sys.stdout) The parser will then exit. 

This is my first patch ever written, so don't be too harsh if it's utter garbage^^
msg112310 - (view) Author: Steven Bethard (bethard) * (Python committer) Date: 2010-08-01 10:50
The patch looks basically right. A few minor issues:

* "message=None," should probably be "message,", that is, message should not be allowed to default to None - I can't see any use case for this action without a message. I believe this means the body of __call__ can be simplified to::

  self.file.write(self.message)
  self.file.write("\n")
  parser.exit()

* The other thing the patch needs is to update the test suite to add tests to make sure this behavior works. Take a look at test_argparse.py for how to do that.

* The last thing is that to have the greatest chance of having someone check this in, you'll want to make your patch against Python trunk as explained here:

http://www.python.org/dev/faq/#how-do-i-get-a-checkout-of-the-repository-read-only-or-read-write
http://www.python.org/dev/faq/#how-to-make-a-patch

Thanks!
msg122031 - (view) Author: Éric Araujo (eric.araujo) * (Python committer) Date: 2010-11-22 00:07
sys.std* should not be used as default values in a function definition, because they may be rebound to other objects.  The usual idiom is to have None as default value and check it at call time.

The patch also needs tests and docs.

(FTR, the example for callable in this report was wrong: First, the message argument was missing in the lambda, second, there was no need for a lambda in the first place :)
msg124495 - (view) Author: Éric Araujo (eric.araujo) * (Python committer) Date: 2010-12-22 09:47
Thinking again about that, what’s wrong with argparse replacing \n with spaces and doing its own line wrapping?
msg124509 - (view) Author: Dennis Malcorps (travistouchdown) Date: 2010-12-22 15:47
I totally forgot about this patch, sorry... Due to university and another python project I am working on I didn't find the time to look at the test suite. I would really appreciate it if someone else could do this ;-)

Anyway, I added the changes proposed by Steven Bethard and Éric Araujo regarding the default argument values. (patch made against trunk)

@Éric:
Well, first there is currently only the 'version' action which does this and imho it just looks strange if you try to print something else than a version info with it.
Second, there are cases where you want whitespaces to be preserved, like printing out the license of your program (as I mentioned in my first post) Just look at this BSD license:

Copyright (c) 2010, Dennis Malcorps <dennis.malcorps@gmail.com> Redistribution
and use in source and binary forms, with or without modification, are
permitted provided that the following conditions are met: 1. Redistributions
of source code must retain the above copyright notice, this list of conditions                                                                       
and the following disclaimer. 2. Redistributions in binary form must reproduce                                                                       
the above copyright notice, this list of conditions and the following                                                                                
disclaimer in the documentation and/or other materials provided with the                                                                             
distribution. 3. The name of the author may not be used to endorse or promote                                                                        
products derived from this software without specific prior written permission.                                                                       
THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED                                                                           
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF                                                                                 
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO                                                                           
EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.


This is just plain ugly.
msg127855 - (view) Author: Éric Araujo (eric.araujo) * (Python committer) Date: 2011-02-04 01:47
Thanks for the new patch.  It does not apply cleanly to the py3k branch, but that’s very easily corrected.


        self.file.write(self.message)
        self.file.write('\n')

Could be replaced by
        print(self.message, file=self.file)

print will use the right EOL for the platform and is just coooler.


By the way, self.file should not be rebound in __call__:
        if self.file is None:
            self.file = _sys.stdout
→
        file = (self.file if self.file is not None else _sys.stdout)


> Second, there are cases where you want whitespaces to be preserved,
> like printing out the license of your program (as I mentioned in my
> first post) Just look at this BSD license: [...]
> This is just plain ugly.

Agreed, but I did not suggest that the output be like that.  In my previous message, I suggested that argparse could replace \n with spaces and do its own line wrapping, to adapt to terminal length while avoiding chunking ugliness.  That’s Steven’s call anyway, I don’t have any strong feeling in favor of preserving whitespace or rewrapping.  If argparse wraps lines to the terminal width in other cases (like with epilog text), I think it should do so here too.
msg128267 - (view) Author: Steven Bethard (bethard) * (Python committer) Date: 2011-02-10 07:20
Argparse's wrapping behavior is determined by the formatter_class:

http://docs.python.org/library/argparse.html#formatter-class

Is it reasonable to assume that if you're wrapping your own messages you're already specifying formatter_class=argparse.RawTextHelpFormatter? If so, then perhaps the message should be printed via something like:

    formatter = parser._get_formatter()
    formatter.add_text(self.message)
    file.write(formatter.format_help())

This is what we do in _VersionAction, so I guess it's probably what we should do here.
msg223198 - (view) Author: Mark Lawrence (BreamoreBoy) * Date: 2014-07-16 13:40
@Paul what is your take on this, other opinions seem positive?
msg223483 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2014-07-19 22:17
As Steven notes, the patch lacks tests.  It also lacks documentation.  

The 2 things that this class does different from 'version' are
- write without passing the text through 'textwrap'
- write to a user defined file

There is a difference in opinion between Éric and Steven as to whether the class should use write directly or use the HelpFormatter.

I don't think it needs further action at this time.  There doesn't seem to be a lot of interest in it.  Also there are a number of ways of accomplishing the task without adding an Action class.

- the class is a simple adaptation of the 'version' class

- a user defined class works just as well

- 'version' with Raw formatter, and shell redirection also works

- it is also easy to write the text to a file after parse_args.

I've attached a file that illustrates a number of these alternatives.  It includes a 'callable' class.
-----------------

The discussion got me thinking about a selective version of the RAW formatting, analogous to the HTML <pre> tag.  With this the user could mark a given text (description, epilog, help, version etc) as pre-formatted, without having to change the formatter_class.

    parser.add_argument('--license', action='version',
        version=Pre(formatted_text))

I probably should submit that in a new issue.
msg356412 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2019-11-12 05:44
Marking this as closed because there has been no activity or interest for over five years.

If anyone wants to revive this and write a PR, feel free to reopen.  The core idea is plausible, but this doesn't seem to be a recurring need.
History
Date User Action Args
2022-04-11 14:57:04adminsetgithub: 53645
2019-11-12 05:44:31rhettingersetstatus: open -> closed
resolution: out of date
messages: + msg356412

stage: test needed -> resolved
2019-08-30 03:30:58rhettingersetassignee: bethard -> rhettinger

nosy: + rhettinger
2019-04-26 20:02:38BreamoreBoysetnosy: - BreamoreBoy
2014-07-19 22:17:11paul.j3setfiles: + try_write.py

messages: + msg223483
2014-07-16 16:10:50berker.peksagsetnosy: + berker.peksag
2014-07-16 13:40:00BreamoreBoysetnosy: + paul.j3, BreamoreBoy

messages: + msg223198
versions: + Python 3.5, - Python 3.3
2011-12-16 00:34:35denilsonsasetnosy: + denilsonsa
2011-02-10 07:20:02bethardsetnosy: georg.brandl, bethard, eric.araujo, travistouchdown
messages: + msg128267
2011-02-04 01:47:13eric.araujosetnosy: georg.brandl, bethard, eric.araujo, travistouchdown
messages: + msg127855
2010-12-22 15:47:23travistouchdownsetfiles: + argparse.diff
nosy: georg.brandl, bethard, eric.araujo, travistouchdown
messages: + msg124509
2010-12-22 09:47:20eric.araujosetnosy: georg.brandl, bethard, eric.araujo, travistouchdown
messages: + msg124495
versions: + Python 3.3, - Python 3.2
2010-11-22 00:07:16eric.araujosetnosy: + eric.araujo

messages: + msg122031
stage: test needed
2010-08-01 10:50:23bethardsetmessages: + msg112310
2010-07-30 13:32:10travistouchdownsetfiles: + argparse.diff
keywords: + patch
messages: + msg112076
2010-07-30 06:54:03bethardsetmessages: + msg112042
2010-07-29 15:22:21travistouchdownsetmessages: + msg111949
2010-07-29 14:26:17bethardsetmessages: + msg111936
2010-07-29 13:47:30georg.brandlsetassignee: bethard

messages: + msg111924
nosy: + georg.brandl
2010-07-29 02:43:17r.david.murraysetnosy: + bethard

versions: - Python 2.7
2010-07-28 15:10:39travistouchdowncreate