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.

Unsupported provider

classification
Title: Add "os.get_terminal_size()" function
Type: enhancement Stage: resolved
Components: Library (Lib) Versions: Python 3.3
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: Nosy List: Arfrever, amaury.forgeotdarc, denilsonsa, eric.araujo, giampaolo.rodola, loewis, neologix, pitrou, python-dev, rosslagerwall, techtonik, vstinner, zbysz
Priority: normal Keywords: patch

Created on 2011-12-16 01:01 by denilsonsa, last changed 2022-04-11 14:57 by admin. This issue is now closed.

Files
File name Uploaded Description Edit
termsize.diff zbysz, 2011-12-16 16:09 review
termsize.diff.1 zbysz, 2011-12-17 01:41 review
termsize.diff.2 zbysz, 2011-12-31 14:01 third version of the patch, after review review
termsize.diff.3 zbysz, 2012-01-02 22:43 fourth version of the patch, after more reviews
termsize.diff.4 zbysz, 2012-01-06 12:30
termsize.diff.5 zbysz, 2012-01-06 18:55 sixth version of the patch, after review
termsize.diff.6 zbysz, 2012-01-30 17:00
termsize.diff.7 zbysz, 2012-01-30 23:25 eighth version of the patch
termsize.diff.8 zbysz, 2012-01-31 16:36
get_terminal_size_pipe.patch vstinner, 2012-02-12 23:02 review
get_terminal_size_pipe-2.patch vstinner, 2012-02-12 23:25 review
Messages (71)
msg149586 - (view) Author: Denilson Figueiredo de Sá (denilsonsa) Date: 2011-12-16 01:01
Please add a function called "os.get_terminal_size()" that should return a tuple "(width, height)".

The built-in "argparse" module would directly benefit from this function, as it would wrap the help text correctly. I'm pretty sure many users would also benefit from it.

Once this function gets implemented, issue #13041 can be trivially fixed. There are some suggestions about how to implement this feature in issue #8408.

[extra feature:] After this function gets implemented, it might be a good idea to implement some kind of API to detect size changes without requiring probing. (or, at least, the documentation should give some directions about how to achieve it)
msg149595 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2011-12-16 06:53
Well, do you want to propose a patch yourself?
See http://docs.python.org/devguide/ for how to contribute a patch.
msg149597 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2011-12-16 08:14
The patch is already almost there (in #13041). I'll post an updated version here in a moment.
msg149613 - (view) Author: Denilson Figueiredo de Sá (denilsonsa) Date: 2011-12-16 11:28
Zbyszek, I just looked at [1] and I disagree that the environment variable should have higher precedence. In fact, I believe it should have lower precedence, and should be used as a fallback.

[1]: http://bugs.python.org/file23241/patch1.1.diff

Reason? Imagine a program is launched, and thus it has COLUMNS set to some value. While the program is running, the user resizes the terminal. I believe (though I have not tested) that the envvar will keep the old value, and this function would return wrong dimensions.

In my opinion, the system-specific code (termios/windll) should be tried first, and then the envvars (as fallback). If all else fails, maybe it would be better to return (None, None) and let the caller of this function handle that.
msg149616 - (view) Author: Giampaolo Rodola' (giampaolo.rodola) * (Python committer) Date: 2011-12-16 12:07
http://bugs.python.org/file23241/patch1.1.diff
This looks like something which would fit better into shutil module rather than os.
Also, the Windows implementation should not rely on ctypes.
msg149617 - (view) Author: Giampaolo Rodola' (giampaolo.rodola) * (Python committer) Date: 2011-12-16 12:16
Plus, you should provide also heigth, not only width, possibly as a namedtuple:

>>> import shutil
>>> shutil.get_terminal_size()
size(width=80, heigth=170)
>>>
msg149620 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2011-12-16 13:41
One reply to three comments:

[To myself]:
>  I'll post an updated version here in a moment.
Hm, it's not so simple. The implementation is simple,
but the configure defines are harder than I thought.
But I'm getting there :)

> Zbyszek, I just looked at [1] and I disagree that the environment variable should have higher precedence. In fact, I believe it should have lower precedence, and should be used as a fallback.

I disagree. $COLUMNS will usually _not_ be set:

$ echo $COLUMNS
119

$ ./python -c 'import os; print("COLUMNS" in os.environ)'
False

As I wrote before, the shell only sets COLUMNS for use in the shell, without exporting. Giving a precedence to the environment variable (if it is set by the user) allows to do something like this:
$ COLUMNS=80 python program.py
... do whatever is to be dome for 80 columns ...
and the normal case (with dynamic checking) will also work correctly.

Anyway, I think that two functions should be provided: a "raw" one, which does the ioctl, and the "normal" one, which queries $COLUMNS and the the "raw" function. If different behaviour is wanted, than just
the "raw" one can be called.

> Also, the Windows implementation should not rely on ctypes.
Of course. For windows I have:
  #include <conio.h>
  ...
  GetConsoleScreenBufferInfo(handle, &csbi)
  ...

> This looks like something which would fit better into shutil module rather than os.
This is problematic. The docstring for shutil says:
  Utility functions for copying and archiving files and directory trees.
So it doesn't seem to fit at all.

OTOH, there's termios and tty, but they are UNIX only. Module curses is also UNIX only, and slightly different topic, because get_terminal_width should be independent of curses.

> Plus, you should provide also heigth, not only width, possibly as a namedtuple.
Agreed.
msg149627 - (view) Author: Giampaolo Rodola' (giampaolo.rodola) * (Python committer) Date: 2011-12-16 15:50
> The docstring for shutil says: Utility functions for copying and 
> archiving files and directory trees. So it doesn't seem to fit at all.

Well... shutil should stand for "shell utilities" and it already contains stuff which is not strictly related to file/directory direct operations (see shutil.disk_usage and upcoming shutil.which).

But maybe you're right... I don't know...
My point is that a function which attempts different fallbacks (ask OS -> ask os.environ -> return (None, None) / raise exception) sounds more like an utility function rather than something belonging to os module, where you tipically see 1-to-1 interfaces for the underlying system functions.
msg149629 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2011-12-16 16:09
This is proof-of-concept implementation.

This adds two modules: termsize (python) and _termsize (C). The first one contains the get_terminal_size user-facing function and namedtuple definition. The second on contains the function query_terminal_size which does the real job.

Two simple tests are added. I'm not sure if it is feasible to test with different terminal sizes: it certainly _could_ be done, e.g. by launching an xterm with a specified geometry, but this would be much
more complicated than this implementation, so probably not worth it.

This was only tested on 64-bit linux, seems to work.

I'm not sure how to the configure tests should be done: right now the presence of <sys/ioctl.h> is checked, and it is used. If not available, 
<conio.h> is checked, and used. Otherwise NotImplementedError is thrown.

Would be nice to test on a mac and other systems, but I don't have one available at the moment unfortunately.

I think that the python part (termsize.py) should be either merged with os.py (or whatever home we find for this function), or rewritten in C in _termsizemodule.c and only imported into os. But since it hasn't yet been decided where this should go, keeping it separate is easier for now.
msg149630 - (view) Author: Amaury Forgeot d'Arc (amaury.forgeotdarc) * (Python committer) Date: 2011-12-16 16:45
Don't forget the Windows platform... here is an implementation: https://bitbucket.org/hpk42/py/src/980c8d526463/py/_io/terminalwriter.py#cl-284 
But it would be better written in C, of course.
msg149631 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2011-12-16 17:32
Ok, a couple of general comments:
- there is no point having a separate module for a single function; I think the os module (and posixmodule.c for the C side) is a reasonable place where to put this
- C code should be indented with 4 spaces increments and no tabs (see PEP 7)
- constants in C code should be uppercase
- C code should be C89-compliant and therefore we don't use named struct initializers (such as ".m_size = 0")
msg149653 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2011-12-17 01:41
Here's updated version: termsize.diff.1

> Ok, a couple of general comments:
> - there is no point having a separate module for a single function; I > think the os module (and posixmodule.c for the C side) is a
> reasonable place where to put this
Done. (But posixmodule.c is so enourmous... I feel bad making it even longer.)

> - C code should be indented with 4 spaces increments and no tabs (see > PEP 7)
> - constants in C code should be uppercase
> - C code should be C89-compliant and therefore we don't use named
> struct initializers (such as ".m_size = 0")
All done, I hope.

This version was tested on linux/amd64 and win32 (XP).
msg150287 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2011-12-28 13:08
Seems to work also on kfreebsd/debian (with eglibc).
msg150388 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2011-12-30 20:16
(review posted on http://bugs.python.org/review/13609/show )
msg150419 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2011-12-31 14:01
Thanks for the review!

New version is attached. The code is actually slightly shorter, but
there are more docs.

 Doc/library/os.rst        |   52 +++++++++++++++++++
 Doc/whatsnew/3.3.rst      |    5 +
 Lib/os.py                 |   43 +++++++++++++++
 Lib/test/test_termsize.py |   31 +++++++++++
 Misc/NEWS                 |    3 +
 Modules/posixmodule.c     |  125 ++++++++++++++++++++++++++++++++++++++++++++++
 configure                 |    2 
 configure.in              |    2 
 pyconfig.h.in             |    3 +
 9 files changed, 264 insertions(+), 2 deletions(-)

>The patch also lacks some documentation update in Doc/library/os.rst.
Added as a subsection under "File Descriptor Operations" section.
I also added an entry to Misc/NEWS and Doc/whatsnew/3.3.rst.

> Lib/os.py:833: def get_terminal_size(columns=80, rows=25):
> The arguments seem ignored in the function body, so I think they should simply
> be removed.
The implementation was borked and didn't actually do what the
docstring said. It should be fixed now.

I want to retain the default values, because get_terminal_size()
should "do the right thing" in case stdout is not a terminal,
without the user needing to wrap it in a try..except block
or some other test. Putting the default values as parameters
makes it clear what will be returned in case query fails, and
makes it easy to override those defaults.

To make it clearer that those are the fallback values, I renamed the
parameter to 'fallback=(c, r)', which should be understandable even
without reading the docstring. Having it as a single parameter might
also make it easier to add something like 'minimum=(c, r)' in the
future.

> I wonder if you shouldn't try to explicitly pass sys.stdout.fileno() here, or
> perhaps sys.__stdout__.fileno() since the former could be overriden.
OK, changed to sys.__stdout__.fileno().

I think that sys.__stdout__ is better, because when stdout is overridden,
it is usually with something that is not a terminal. Changing the output
terminal is probably pretty rare.

> Actually, perhaps get_terminal_size() should have an optional file descriptor
> argument.
Querying anything other than stdout should be pretty rare. If
necessary, query_terminal_size() can be called explicitly. Also,
the variables $COLUMNS and $ROWS are usually meant to refer to stdout,
so get_terminal_size() is about stdout and ROWS and COLUMNS, and the
other function allows full control.

> Lib/test/test_termsize.py:1: import unittest
> This file seems to lack an ``if __name__ == '__main__'`` like other test files
OK.

> Besides, why not simply put the tests in test_os.py?
The tests were nicely self-contained. I wanted to be able to do:
  ./python Lib/test/test_termsize.py
and
  ./python Lib/test/test_termsize.py | cat
but I guess it can be easily moved to test_os.py if necessary.

> Lib/test/test_termsize.py:7: def set_environ(**variables):
> There's already EnvironmentVarGuard in test.support.
OK.

> Lib/test/test_termsize.py:7: def set_environ(**variables):
> There's already EnvironmentVarGuard in test.support.
> Lib/test/test_termsize.py:25: self.assertTrue(0 < size.columns)
> Better to use assertGreater here, failure messages will be more informative.
> Lib/test/test_termsize.py:33: self.assertTrue(size.columns == 777)
> And better to use assertEqual here.
OK.

> Typo here ("imlmented").
OK.

> Modules/posixmodule.c:10571: return PyErr_Format(PyExc_ValueError)
> I don't think there's any point in changing the error here. Just let
> a normal OSError be raised.
OK, s/ValueError/OSError/ here and the the windows case below too.

> Modules/posixmodule.c:10573: return PyErr_SetFromErrno(PyExc_IOError);
> For the record, in 3.3, PyExc_IOError is an alias of PyExc_OSError.
OK, s/IOError/OSError/ for clarity.

> Modules/posixmodule.c:10599: return PyErr_Format(PyExc_IOError, "error %i",
> (int) GetLastError());
> Just use PyErr_SetFromWindowsErr(GetLastError()).
OK.

> Modules/posixmodule.c:11086: {"query_terminal_size", query_terminal_size,
> METH_VARARGS, termsize__doc__},
> I don't think there's any point in making this helper function public, so I'd
> rename it _query_terminal_size or something.
The idea is that query_terminal_size() can be used to do the
low-level query ignoring $COLUMNS and $ROWS and returing the real
error if something goes wrong. If is documented, so I think that
it can be "public".

I've also improved the docstrings slightly.
msg150454 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2012-01-02 13:00
Thanks for the updated patch.
I think I would like other people's opinions about the dual-function approach.
msg150456 - (view) Author: Giampaolo Rodola' (giampaolo.rodola) * (Python committer) Date: 2012-01-02 13:55
What I would do:

- build the namedtuple in Python rather than in C
- I don't particularly like CamelCased names for nametuples (I would use "size" instead of "TerminalSize")
- on UNIX, the ioctl() stuff can be done in python rather than in C
- use C only on Windows to deal with GetStdHandle/GetConsoleScreenBufferInfo; function name should be accessed as nt._get_terminal_size similarly to what we did for shutil.disk_usage in issue12442.
- do not raise NotImplementedError; if the required underlying functions are not available os.get_terminal_size should not exists in the first place (same as we did for shutil.disk_usage / issue12442)


Also, I'm not sure I like fallback=(80, 25) as default, followed by:

        try:
            size = query_terminal_size(sys.__stdout__.fileno())
        except OSError:
            size = TerminalSize(fallback)

That means we're never going to get an exception.
Instead I would:
- provide fallback as None
- let OSError propate unless fallback is not None
msg150462 - (view) Author: Ross Lagerwall (rosslagerwall) (Python committer) Date: 2012-01-02 16:46
Nice patch :-)

I think the two function approach works well.

Since you have already checked that termsize is not NULL, Py_DECREF can be used instead of Py_CLEAR.

Would it not be better to use sys.__stdout__ instead of 1 in the documentation and to use STDOUT_FILENO instead of 1 in the code?

A brief google suggested that Solaris requires sys/termios.h for TIOCGWINSZ. Perhaps also only define TERMSIZE_USE_IOCTL if TIOCGWINSZ is defined.
Like so:
#if defined(HAVE_SYS_IOCTL_H)
#include <sys/ioctl.h>
#if defined(TIOCGWINSZ)
#define TERMSIZE_USE_IOCTL
#else
#define TERMSIZE_USE_NOTIMPLEMENTED
#endif
#elif defined(HAVE_CONIO_H)
#include <windows.h>
#include <conio.h>
#define TERMSIZE_USE_CONIO
#else
#define TERMSIZE_USE_NOTIMPLEMENTED
#endif

(didn't check the windows parts)
msg150474 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2012-01-02 18:57
Thanks for the reviews!

> - build the namedtuple in Python rather than in C
I started this way, but if two functions are used, it is nicer to have them both
return the same type. If it was defined in the Python part, there would be
loop in the sense that os would import the function from _posixmodule, and
_posixmodule would import the namedtuple from os. The code for the tuple is fairly simple,
mostly docs, and saving a few lines just doesn't seem to be worth the complication of doing
it other way around.

> - I don't particularly like CamelCased names for nametuples (I would use "size" instead of "TerminalSize")
I was following the style in _posixmodule: there is WaitidResultType and SchedParamType.
"size" seems to generic, maybe something like "terminal_size" would be better (SchedParamType
is exported as sched_param).

Will change to "terminal_size".

- on UNIX, the ioctl() stuff can be done in python rather than in C
It can, but it requires extracting the bytes by hand, doing it in C is cleaner.
The C implementation is only 5 lines (in addition to the code necessary for windows)
and again it seems simpler this way. Also the configuration is kept in one
place in the #ifdefs at the top, not in two different files.

> - use C only on Windows to deal with GetStdHandle/GetConsoleScreenBufferInfo; function name should be
> accessed as nt._get_terminal_size similarly to what we did for shutil.disk_usage in issue12442.
> - do not raise NotImplementedError; if the required underlying functions are not available 
> os.get_terminal_size should not exists in the first place (same as we did for shutil.disk_usage / issue12442)

I think that there is a difference in "importance" -- shutil.disk_usage cannot be
faked, or ignored, and if it is unavailable, then some work-around must be implemented.
OTOH, the terminal size is an embellishment, and if a wrong terminal size is used, the output
might wrap around or be a bit narrow (the common case), but like in argparse.FancyFormatter,
just continuing is probably reasonable. So to save the users of the function the trouble
to wrap it in some try..except, let's cater for the common case.

This same argument goes for defining the function only if an implementation is available:
argparse would need something like:
try:
  os.get_terminal_size
except NameError:
  def get_terminal_size():
    return (80, 25)
which just doesn't seem to _gain_ anything.

> Also, I'm not sure I like fallback=(80, 25) as default, followed by:
> [...]
> That means we're never going to get an exception.
> Instead I would:
> - provide fallback as None
> - let OSError propate unless fallback is not None
Again, if the user forgets to add the fallback, and only tests in a terminal, she 
would get an unnecessary surprise when running the thing in a pipe. So the fallback
would have to be almost always provided... and it will almost always be (80, 25).
So let's just provide it upfront, and let the user call the low-level function if
they want full control.

-----------------------------------------------------------------------------------

> I think the two function approach works well.
:)

> Since you have already checked that termsize is not NULL, Py_DECREF can be used instead of Py_CLEAR.
OK.

> Would it not be better to use sys.__stdout__ instead of 1 in the documentation and to use STDOUT_FILENO 
> instead of 1 in the code?
OK.

> A brief google suggested that Solaris requires sys/termios.h for TIOCGWINSZ. Perhaps also only define
> TERMSIZE_USE_IOCTL if TIOCGWINSZ is defined.
Hm, I don't have solaris available, so I won't be able to check easily, but
maybe sys/termios.h should be imported if TIOCGWINSZ is not available. Would 
be nice to provide the implementation if possible.

> Like so:
Something like:

> #if defined(HAVE_SYS_IOCTL_H)
> #include <sys/ioctl.h>
> #if defined(TIOCGWINSZ)
> #define TERMSIZE_USE_IOCTL
> #else

#if defined(HAVE_SYS_TERMIOS_H)
#include <sys/termios.h>
#if defined(TIOCGWINSZ)
#define TERMSIZE_USE_IOCTL
#else
#define TERMSIZE_USE_NOTIMPLEMENTED

> #endif
> #elif defined(HAVE_CONIO_H)
> #include <windows.h>
> #include <conio.h>
> #define TERMSIZE_USE_CONIO
> #else
> #define TERMSIZE_USE_NOTIMPLEMENTED
> #endif
msg150475 - (view) Author: Ross Lagerwall (rosslagerwall) (Python committer) Date: 2012-01-02 19:49
I'll try & investigate Solaris a bit...

Also, what should be the default terminal size?

Gnome-terminal and xterm seem to default to 80x24, not 80x25.
msg150481 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2012-01-02 22:43
Updated patch termsize.diff.3

- s/TerminalSize/terminal_size/ in Python code
- change the fallback to (80, 24) (seems to be the commonest default)
- s/Py_CLEAR/Py_DECREF/
- use STDOUT_FILENO
- add more hrefs in docs
- include <termios.h> is available (untested fix for solaris compatibility)
- rename TerminalSize as terminal_size in Python code

I tested this iteration only on linux and windows, but it is not substantially changed, so should still work on *bsd. (I previously
tested on debian/kfreebsd and dragonflybsd and it seemed functional).
msg150689 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2012-01-05 20:54
I haven't read much of this issue, but I strongly dislike the use of named tuples. Either we want people to use named fields, then we should use a regular class (possibly with slots), or we want to define the result as two values, then there should be a plain tuple result.

named tuples should only be used as a compatibility mechanism, when the first design used tuples, and it was later found that additional values need to be returned which would change the number of values (or the original design was considered bad because it returned too many positional values to properly keep track).
msg150693 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2012-01-05 21:24
> I haven't read much of this issue, but I strongly dislike the use of 
> named tuples.
I don't really have a very strong opinion, but (cols, rows) does seem a lot like a tuple -- it really is just a pair of values without other function or state. Still I would much prefer to say
  get_terminal_size().columns
than
  get_terminal_size()[0]
So a bare tuple seems like the worst choice.
msg150695 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2012-01-05 21:42
The point of named tuples here is that you can use both
    get_terminal_size().columns
or
    columns, rows = get_terminal_size()
depending on the situation.
Also, the better repr() makes debugging easier.
msg150701 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2012-01-06 00:19
Some comments about termsize.diff.3.

I don't see why there are two functions, one should be enough: get_terminal_size() should be dropped, and query_terminal_size() renamed to get_terminal_size(). As said before, I don't think that reading ROWS and COLUMNS environment variables is useful. If a program chose to rely on these variables, it can reimplement its own "try env var or fallback on get_terminal_size()" function.

get_terminal_size() should not have a fallback value: it should raise an error, and the caller is responsible to decide what to do in this case (e.g. catch the exception and use its own default value). Most functions in the posix module work like this, and it avoids the difficult choice of the right default value. (fallback=None is an hack to avoid an exception, it's not the pythonic.)

I don't think that it's possible that stdin, stdout and/or stderr have its own terminal. I suppose that the 3 streams are always attached to the same terminal. So I don't see why the function would take an argument. Tell me if I am wrong.

Instead of using sys.__stdout__.fileno(), you can directly use 1 because Python always create sys.__stdout__ from the file descriptor 1.

I think that a tuple (columns, rows) would be just fine. A namedtuple helps when you have a variable number of fields, or more than 3 fields, but here you just have 2 fields, it's not too much difficult to remember which one contains the columns.

I would prefer an optional function, instead of implementing a function raising a NotImplementedError. All other posix functions are defined like this.

ioctl() is already exposed in the fcntl module, I don't see a specific test for <sys/ioctl.h> header. It looks like the module is always compiled on Unix, I don't see how fcntl and ioctl are tested in setup.py.

I don't think that you need <conio.h> to get GetConsoleScreenBufferInfo(), <windows.h> should be enough. So just check for "#ifdef MS_WINDOWS".

Your function is helpful, and it is surprising that nobody proposed to implement it in Python. Some libraries did already implement their own function (like the "py" library).
msg150703 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2012-01-06 00:26
> I don't think that it's possible that stdin, stdout and/or stderr have
> its own terminal. I suppose that the 3 streams are always attached to
> the same terminal. So I don't see why the function would take an
> argument. Tell me if I am wrong.

I think it can be useful in case the program creates its own
session/terminal using openpty?

> Instead of using sys.__stdout__.fileno(), you can directly use 1
> because Python always create sys.__stdout__ from the file descriptor
> 1.

From pythonrun.c:

    /* Set sys.stdin */
    fd = fileno(stdin);
    [...]
    /* Set sys.stdout */
    fd = fileno(stdout);
    [...]
    /* Set sys.stderr, replaces the preliminary stderr */
    fd = fileno(stderr);
msg150715 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2012-01-06 06:10
Zitat von Antoine Pitrou <report@bugs.python.org>:

> Antoine Pitrou <pitrou@free.fr> added the comment:
>
> The point of named tuples here is that you can use both
>     get_terminal_size().columns
> or
>     columns, rows = get_terminal_size()
> depending on the situation.

And my point is that we should make the choice which
of these is more obvious, and drop the other.

> Also, the better repr() makes debugging easier.

A class could still have a nice repr.
msg150722 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2012-01-06 12:30
Following comments by Martin and Victor, here is next version: termsize.diff.4

Changes:
- just check for defined(MS_WINDOWS) and rely on <windows.h>.
- rename query_terminal_size to get_terminal_size_raw
     This way it should be clearer that the second one is low-level,
     and should be less exposed. I don't want to call it
     _get_terminal_size() because it is not just an implementation
     detail and would sometimes be called directly.
- NotImplementedError is gone. get_terminal_size_raw() is not
  defined if not possible.

Non-changes:
- sys.__stdout__.fileno() is not changed to 1, because as Antoine
  pointed out, it is set at runtime.
- STDOUT_FILENO: not defined on windows, so just use 1 and add a comment
- fd argument is retained, because we might want to test terminals
  opened with openpty.
- two functions: still there. I think that get_terminal_size() should
  provide an easy-to-use, even trivial-to-use, way to get a sensible
  value without writing a wrapper. In the _majority_ of cases the
  wrapper would be something like get_terminal_size() currently.
- named tuple: like Antoine said, it gives nice syntax.
- testing for <sys/ioctl.h>: in termios and other modules, setup.py
  first tests if we are on unix. But there might be unices without
  TIOCGWINSZ, and non-unix systems where TIOCGWINSZ _is_ defined,
  so it seems a functional test is simpler and more robust.

Tested on linux and windows XP.
msg150731 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2012-01-06 13:27
One more comment on $COLUMNS overriding the actual terminal size:

> Zbyszek, I just looked at [1] and I disagree that the environment
> variable should have higher precedence. In fact, I believe it should
> have lower precedence, and should be used as a fallback.

To fix issue #9553 "test_argparse.py: 80 failures if COLUMNS env var set to a value other than 80", sys.env['COLUMNS']=80 is set during tests.
If issue #13041 is fixed and a real terminal size is used, then
unless $COLUMNS have higher preference, the tests would break again.
msg150732 - (view) Author: Amaury Forgeot d'Arc (amaury.forgeotdarc) * (Python committer) Date: 2012-01-06 13:44
Some remarks on the Windows implementation in termsize.diff.4:

- On Windows, the C runtime always sets fileno(stdout) to 1, so hardcoded values are OK.
But on Unix, I'm quite sure that embedded interpreters (mod_python?) sometimes close the standard descriptor, so fd=1 can refer to something entirely different.
Does it makes sense to initialize fd=fileno(stdout) (this is C code) instead?

- When GetStdHandle() returns INVALID_HANDLE_VALUE, PyErr_SetFromWindowsErr(0) should be used.
And it's not necessary to use GetLastError(), 0 is enough.

- GetStdHandle will return NULL in a non-console application (try with pythonw.exe or IDLE), I think a specific error message should be raised in this case.
msg150759 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2012-01-06 18:55
> Some remarks on the Windows implementation in termsize.diff.4:

> - On Windows, the C runtime always sets fileno(stdout) to 1, so
> hardcoded values are OK.
> But on Unix, I'm quite sure that embedded interpreters (mod_python?) 
> sometimes close the standard descriptor, so fd=1 can refer to 
> something entirely different.
> Does it makes sense to initialize fd=fileno(stdout) (this is C code) 
> instead?
OK, I agree that fd=fileno(stdout) is better. Now it matches the docstring which says "defaults to stdout" better.

> - When GetStdHandle() returns INVALID_HANDLE_VALUE, 
> PyErr_SetFromWindowsErr(0) should be used.
OK.
> And it's not necessary to use GetLastError(), 0 is enough.
OK, good no know.

> - GetStdHandle will return NULL in a non-console application (try 
> with pythonw.exe or IDLE), I think a specific error message should be 
> raised in this case.
OK. I tried and I see that the GetStdHandle documentation says so, but
I couldn't get it to return NULL. Nevertheless, I added a check:
+        if (handle == NULL)
+            return PyErr_Format(PyExc_OSError, "stdout not connected");

Thanks for the review!

Updated: termsize.diff.5
msg150775 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2012-01-06 22:44
> - fd argument is retained, because we might want to test terminals
>  opened with openpty.

You mean a terminal different than the one used for stdin, stdout and stderr?

> - two functions: still there. I think that get_terminal_size() should
>  provide an easy-to-use, even trivial-to-use, way to get a sensible
>  value without writing a wrapper. In the _majority_ of cases the
>  wrapper would be something like get_terminal_size() currently.

get_terminal_size() looks like reliable than raw_get_terminal_size()
because environment variables are not updated when the terminal is
resized.
msg150776 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2012-01-06 23:07
>> - fd argument is retained, because we might want to test terminals
>>  opened with openpty.
>
> You mean a terminal different than the one used for stdin, stdout and 
> stderr?

For example, let's see what is the size of my two xterms:

>>> os.get_terminal_size_raw()
os.terminal_size(columns=125, rows=39)
>>> fd = os.open('/proc/22736/fd/1', os.O_RDONLY)
>>> fd
6
>>> os.get_terminal_size_raw(6)
os.terminal_size(columns=95, rows=33)

I'm not saying that this serves a clear purpose, but it is possible
and it is good not the restrain the API. I'm sure somebody could find
a use for it.

>> - two functions: still there. I think that get_terminal_size() should
>>  provide an easy-to-use, even trivial-to-use, way to get a sensible
>>  value without writing a wrapper. In the _majority_ of cases the
>>  wrapper would be something like get_terminal_size() currently.
>
> get_terminal_size() looks like [less?] reliable than raw_get_terminal_size()
> because environment variables are not updated when the terminal is
> resized.
Please look at my comment above: http://bugs.python.org/issue13609#msg149620
$COLUMNS is only used for overriding -- it normally _isn't_ set.
msg151391 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2012-01-16 17:03
Does this need need more discussion, code review, testing, or just more time?
msg151729 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2012-01-21 13:57
> Does this need need more discussion, code review, testing,
> or just more time?

As I already wrote, I would prefer a very simple os.get_terminal_size() function: don't read environment varaiables, use a simple tuple instead of a new type, and raise an error if the size cannot be read (so no need of default values). The os module is written as a thin wrapper between Python and the OS. A more high level function (read environment variables, handle the error, use a namedtuple) can be written in your application, or maybe in another module.

This is just my opinion, other core developers may prefer your version :-)
msg151740 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2012-01-21 19:04
> > Does this need need more discussion, code review, testing,
> > or just more time?
> 
> As I already wrote, I would prefer a very simple
> os.get_terminal_size() function: don't read environment varaiables,
> use a simple tuple instead of a new type, and raise an error if the
> size cannot be read (so no need of default values). The os module is
> written as a thin wrapper between Python and the OS. A more high level
> function (read environment variables, handle the error, use a
> namedtuple) can be written in your application, or maybe in another
> module.

I think we have reached the point where we won't be in total agreement
over the API, so let's choose whatever is submitted as a patch.

I only have two remaining issues with the patch:
- the tests needn't be in a separate file, they can go in test_os
- there should be a test for get_terminal_size_raw as well (and of
course it should be skipped if the function doesn't exist)
msg151742 - (view) Author: Giampaolo Rodola' (giampaolo.rodola) * (Python committer) Date: 2012-01-21 19:40
> read environment varaiables [...] and raise an error if the size cannot be
> read (so no need of default values). The os module is written as a thin
> wrapper between Python and the OS. A more high level function (read 
> environment variables, handle the error, use a namedtuple) can be written in 
> your application, or maybe in another module.

+1. I also find weird that a function, especially one living in the os module, has such a high level of abstraction (basically this is why I was originally proposing shutil module for this to go in).

Given the different opinions about the API, I think it's best to expose the lowest level functionality as-is, and let the user decide what to do (read env vars first, suppress the exception, use a fallback, etc.). 


> I think we have reached the point where we won't be in total 
> agreement over the API, so let's choose whatever is submitted 
> as a patch.

I'd be more careful. Once this gets in it will be too late for a change.
msg151749 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2012-01-21 22:47
> +1. I also find weird that a function, especially one living in the os
> module, has such a high level of abstraction (basically this is why I
> was originally proposing shutil module for this to go in).
> 
> Given the different opinions about the API, I think it's best to
> expose the lowest level functionality as-is, and let the user decide
> what to do (read env vars first, suppress the exception, use a
> fallback, etc.). 

Fair enough, but other people expressed sympathy for the two-function
approach :) I'm personally indifferent, although I find
"get_terminal_size_raw" a bit ugly and liked "query_terminal_size"
better.

(and looking up ROWS and COLUMNS make sense, since they are de facto
standards)
msg151755 - (view) Author: Denilson Figueiredo de Sá (denilsonsa) Date: 2012-01-22 00:20
On Sat, Jan 21, 2012 at 17:40, Giampaolo Rodola' <report@bugs.python.org> wrote:
>
> Given the different opinions about the API, I think it's best to expose the lowest
> level functionality as-is, and let the user decide what to do (read env vars first,
> suppress the exception, use a fallback, etc.).

As a Python user (and not a committer), I disagree.

As an user, I don't care too much where the function should be placed
(although I believe os or sys are sensible choices). What I do care is
that I want a extremely simple function that will "just work". Don't
make me add code for handling all the extra cases, such code should be
inside the function.

All this discussion about the API made me remember this presentation:
http://python-for-humans.heroku.com/

Also, I see no downside of using a Named Tuple. Issue 4285 actually
added a named tuple to the sys.version_info.
msg151772 - (view) Author: Charles-François Natali (neologix) * (Python committer) Date: 2012-01-22 13:09
> As a Python user (and not a committer), I disagree.
>
> As an user, I don't care too much where the function should be placed
> (although I believe os or sys are sensible choices). What I do care is
> that I want a extremely simple function that will "just work". Don't
> make me add code for handling all the extra cases, such code should be
> inside the function.

For what it's worth, as a Python committer, I agree with you.
Python is a very high level language, and I think the standard library
should be as natural and offer the same productivity gain as the core
language does.
Exposing to the user a mere wrapper around a syscall/library just
doesn't make sense to me. Sure, it should be made available for those
who want/need to do low-level system programming (and I'm one of
those), but the vast majority of users want something higher level
than the POSIX/Windows library.
msg152330 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2012-01-30 17:00
Here's is an updated version: termsize.diff.6

Following Antoine Pitrou's comment get_terminal_size_raw is renamed back to query_terminal_size.

Tests are moved to test_os.py, and there's a new test for query_terminal_size -- the output from 'stty size' is parsed. If something fails (e.g. stty is not available), the test is skipped.

I noticed that bash uses $LINES, not $ROWS. I have renamed rows to lines everywhere, to follow existing convention.

The big remaining question seems to be one function vs. two functions.
I'm still convinced that the high-level two function is better. This issue originated in the argparse module, which can use proper terminal size to efficiently format messages. Of course argparse must use a function with a fallback -- if it cannot query, some default must be used. So argparse would implement the high-level function anyway. It might just as well be in os, available for others to be used. Anyway, the second function is 18 lines of python code (excluding docstring and whitespace) -- a bit too much to copy and paste.
msg152334 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2012-01-30 17:24
> I noticed that bash uses $LINES, not $ROWS. I have renamed rows to lines everywhere, to follow existing convention.

The stty program has "rows" and "columns" commands to set the terminal
size. The tput programs has "cols" and "lines" commands to get the
terminal size. The curses library uses (y, x), e.g. it has a
getmaxyx() function. I don't know the most common term, lines or rows.

Extract of http://stackoverflow.com/questions/1780483/lines-and-columns-environmental-variables-lost-in-a-script
"The variables $LINES and $COLUMNS are shell variables, not
environmental variables, and thus are not exported to the child
process (...)"

If I understood correctly, LINES and COLUMNS environment variables are
only set by the user. In a bash script, these variables are wrapper to
ioctl().

The tput program reads TERM environment variable to decide if LINES
and COLUMNS are used or not:
"-Ttype indicates  the type of terminal.  Normally this option is
unnecessary, because the default is taken from the environment
variable TERM.  If -T is specified, then the shell variables LINES and
COLUMNS will be ignored,and the operating system will not be queried
for the actual screen size."

Extract of http://pubs.opengroup.org/onlinepubs/7908799/xbd/envvar.html :
------
COLUMNS
    A decimal integer > 0 used to indicate the user's preferred width
in column positions for the terminal screen or window. (See column
position .) If this variable is unset or null, the implementation
determines the number of columns, appropriate for the terminal or
window, in an unspecified manner. When COLUMNS is set, any
terminal-width information implied by TERM will be overridden. Users
and portable applications should not set COLUMNS unless they wish to
override the system selection and produce output unrelated to the
terminal characteristics. The default value for the number of column
positions is unspecified because historical implementations use
different methods to determine values corresponding to the size of the
screen in which the utility is run. This size is typically known to
the implementation through the value of TERM or by more elaborate
methods such as extensions to the stty utility, or knowledge of how
the user is dynamically resizing windows on a bit-mapped display
terminal. Users should not need to set this variable in the
environment unless there is a specific reason to override the
implementation's default behaviour, such as to display data in an area
arbitrarily smaller than the terminal or window.

LINES
    A decimal integer > 0 used to indicate the user's preferred number
of lines on a page or the vertical screen or window size in lines. A
line in this case is a vertical measure large enough to hold the
tallest character in the character set being displayed. If this
variable is unset or null, the implementation determines the number of
lines, appropriate for the terminal or window (size, terminal baud
rate, and so forth), in an unspecified manner. When LINES is set, any
terminal-height information implied by TERM will be overridden. Users
and portable applications should not set LINES unless they wish to
override the system selection and produce output unrelated to the
terminal characteristics. The default value for the number of lines is
unspecified because historical implementations use different methods
to determine values corresponding to the size of the screen in which
the utility is run. This size is typically known to the implementation
through the value of TERM or by more elaborate methods such as
extensions to the stty utility, or knowledge of how the user is
dynamically resizing windows on a bit-mapped display terminal. Users
should not need to set this variable in the environment unless there
is a specific reason to override the implementation's default
behaviour, such as to display data in an area arbitrarily smaller than
the terminal or window.
------

> The big remaining question seems to be one function vs. two functions.

Not exactly. I suggested to keep only the simple function in the os
module, and add maybe a function with a higher level API (using
environment variables) in another module (e.g. in shutil).
msg152360 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2012-01-30 23:25
Updated version following comments by Victor Stinner: termsize.diff.7

- os.get_terminal_size() is moved to shutil.get_terminal_size()
- some small doc updates

Victor STINNER wrote:
>> I noticed that bash uses $LINES, not $ROWS. I have renamed rows to
>> lines everywhere, to follow existing convention.
> The stty program has "rows" and "columns" commands to set the terminal
Yes, also struct winsize uses 'rows' and 'cols'. But since we use the variable $LINES, it seems better to use 'lines'.

Thanks for the link to SUSv2! I've added a link in the docs, since it adds a nice justification to the COLUMNS/query/fallback behaviour.

Now get_terminal_size() lives in shutil. I don't think it matters much, but I see the point of keeping os clean.

I also looked at some programs, to see who behaves how.
dpkg -> COLUMNS first
aptitude -> ignores COLUMNS
git -> COLUMNS first
systemctl, loginctl, systemd-cgls (systemd cmdline interface) -> COLUMNS first
less, w ->  ioctl, COLUMNS as fallback

(Of course this is very unscientific, because I just picked some programs at random which I remembered to care about the terminal size).
I guess that the case of 'less' is special, because less is very
much dependent on the terminal and does quite a lot of manipulations on it. Otherwise, taking COLUMNS as highest priority seems well established, even if not universal.
msg152388 - (view) Author: anatoly techtonik (techtonik) Date: 2012-01-31 14:22
Terminal stuff is irrelevant to `shutil`, which is the module for 'High-level file operations' and deserve a separate module named 'console'.

Why? Because terminal size is only relevant when you want to page output. The next step would be to detect if terminal supports color to see if ANSI sequences will be collapsed. Then you'll want to get user input without pressing '<enter>' key.

It is a whole new module.
msg152389 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2012-01-31 14:31
"except Exception" clauses in the tests are too broad. Otherwise, looks fine.
msg152390 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2012-01-31 15:03
termsize.diff.7:
 - shutil.get_terminal_size(): I would prefer columns and lines
variable names instead of "ccc" and "rrr"
 - "Right now the values are not cached, but this might change." why
would it be cached? this sentence can just be removed
 - I would prefer os.get_terminal_size() instead of
os.query_terminal_size(), I didn't know other function using the verb
"query" in Python
 - To keep os simple, os.query_terminal_size() can return a simple,
whereas shutil.get_terminal_size() returns a namedtuple
 - test_os.py: use @unittest.skipUnless() on TermsizeTests to check if
os.query_terminal_size() is available
 - test.py, test_does_not_crash() catchs OSError, you may only ignore
some error codes, not any. For example, skip the test if stdout is not
a tty

+        try:
+            size = os.query_terminal_size(sys.__stdout__.fileno())
+        except (NameError, OSError):
+            size = os.terminal_size(fallback)

AttributeError should be catched here, not NameError. Or better, check
if os has a query_terminal_size() function.

+.. function:: get_terminal_size(fallback=(columns, lines))

Hum, you may document the default value: (80, 24).

shutil.get_terminal_size() doesn't allow to choose the file
descriptor. Would it be useful to allow to get the size of sys.stderr
or another TTY?
msg152394 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2012-01-31 16:36
Well, right now it's just one function. Functionality which you propose
could of course be useful, but let's leave if for another day, another 
issue.

See also http://bugs.python.org/issue13609#msg149627 and #444582 and 
#12442 -- shutil is growing in different directions, and terminal size 
can be one of them.

 > Antoine Pitrou added the comment:
 > "except Exception" clauses in the tests are too broad.
Fixed.
 > Otherwise, looks fine.

 > STINNER Victor added the comment:
 > - shutil.get_terminal_size(): I would prefer columns and lines
 > variable names instead of "ccc" and "rrr"
Changed (this was a leftover from the version when there was no named 
tuple).

 > - I would prefer os.get_terminal_size() instead of
 > os.query_terminal_size(), I didn't know other function using the verb
 > "query" in Python
Changed. I used os.g_t_s and shutil.g_t_s in docs so it should be clear 
which is which.

 > - To keep os simple, os.query_terminal_size() can return a simple,
 > whereas shutil.get_terminal_size() returns a namedtuple
We would have two very similar functions returning something different. 
Also, the amount of code saved will be negligible, because the named 
tuple is mostly docs. I prefer to keep it in os to keep both functions 
similar.

 > - test_os.py: use @unittest.skipUnless() on TermsizeTests to check if
 > os.query_terminal_size() is available
> - To keep os simple, os.query_terminal_size() can return a simple,
> whereas shutil.get_terminal_size() returns a namedtuple
> AttributeError should be catched here, not NameError. Or better, check
> if os has a query_terminal_size() function.
Fixed. I changed the tests a bit, e.g. to take into account that stty 
queries stdin, not stdout.

 > Hum, you may document the default value: (80, 24).
Done.

shutil.get_terminal_size() is tied to COLUMNS and ROWS and thus makes 
most sense for stdout. But I guess that an optional parameter could be 
added:
   shutil.get_terminal_size(fd=None, fallback=(80, 24))
where fd could be either an integer, or an object with .fileno().
I don't know.

 > - "Right now the values are not cached, but this might change." why
 > would it be cached? this sentence can just be removed
Done. In theory it could be cached with watching for SIGWINCH, but I'll 
just remove the comment for now.

Thanks for the review and comments!

Patch version nine attached: termsize.diff.8
msg152395 - (view) Author: anatoly techtonik (techtonik) Date: 2012-01-31 16:45
What happens with COLUMNS env variables if terminal is resized after the Python script is executed. Will get_terminal_size() return new value?
msg152396 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2012-01-31 16:48
> the Python script is executed. Will get_terminal_size()
 > return new value?
Please see previous discussion and the docs (and the SUSv2 specs).
The env. var. is for overriding.
msg152397 - (view) Author: anatoly techtonik (techtonik) Date: 2012-01-31 16:53
On Tue, Jan 31, 2012 at 7:48 PM, Zbyszek Szmek <report@bugs.python.org>wrote:

>
> Zbyszek Szmek <zbyszek@in.waw.pl> added the comment:
>
> > the Python script is executed. Will get_terminal_size()
>  > return new value?
> Please see previous discussion and the docs (and the SUSv2 specs).
> The env. var. is for overriding.
>

Could you just answer the question or provide a relevant link
(unfortunately I don't have time to read SUSv2 specs)?
msg152398 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2012-01-31 16:59
"The env. var. is for overriding."

http://bugs.python.org/issue13609#msg149620
http://bugs.python.org/issue13609#msg152334
msg152807 - (view) Author: anatoly techtonik (techtonik) Date: 2012-02-07 12:38
All right, I've found some time to grep conversation related to COLUMNS/ROWS environment/shell variable.

+1 for low level system wrapper to get current stdout console size
-1 on COLUMN/ROWS "business logic"


My user story 001:
I need exact values of my console and don't want them to be overridden by environment/shell variables. 
-- or --
When troubleshooting problems with console width on user side and don't want this feature to implicitly depend on some variables in user environment.
msg152808 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2012-02-07 13:09
> +1 for low level system wrapper to get current stdout console size

So use os.get_terminal_size()

> -1 on COLUMN/ROWS "business logic"

So don't use shutil.get_terminal_size(), but it looks like their is a
use case for this feature.
msg152913 - (view) Author: Roundup Robot (python-dev) (Python triager) Date: 2012-02-08 22:31
New changeset c92f8de398ed by Antoine Pitrou in branch 'default':
Issue #13609: Add two functions to query the terminal size:
http://hg.python.org/cpython/rev/c92f8de398ed
msg152914 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2012-02-08 22:33
The patch is finally committed. Thank you Zbyszek for having been quite patient.
(according to Murphy's laws, this will surely break some buildbots)
msg152915 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2012-02-08 22:59
Thanks for merging!

I'll try to keep an eye on the buildbot results, but please add me to 
any bugs in case I miss something.
msg153184 - (view) Author: Arfrever Frehtes Taifersar Arahesis (Arfrever) * (Python triager) Date: 2012-02-12 06:09
test_stty_match() should be skipped also when os.isatty(sys.__stdin__.fileno()) is False.
This test fails when test suite is run by Portage (Gentoo package manager), which somehow sets null input.
msg153185 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2012-02-12 06:38
Hi,

looking at the tests, the test should be skipped with 'stty invocation 
failed'. Does something different happen? Can you post the output from 
the tests?
msg153231 - (view) Author: Arfrever Frehtes Taifersar Arahesis (Arfrever) * (Python triager) Date: 2012-02-12 22:00
Actually this test fails due to another reason. I'm still investigating it.

In the meantime, a different bug was found:

# stty size | cat
46 157
# python3.3 -c 'import os; print(os.get_terminal_size())' | cat
Traceback (most recent call last):
  File "<string>", line 1, in <module>
OSError: [Errno 22] Invalid argument

Especially redirection to `less` is often used. `stty size` works, so it should be possible to fix Python.
msg153232 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2012-02-12 23:02
Using strace, I see that stty calls ioctl(TIOCGWINSZ) on stdin (fd=0)
if it failed on stdout (fd=1), whereas Python only tries stdout.

Attached patch implements a similar idea.
msg153233 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2012-02-12 23:13
Your patch is wrong. It always discards ioctl(stdout), even if it was successful.
msg153235 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2012-02-12 23:25
New try (I fixed my email address and the patch).
msg153236 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2012-02-12 23:28
> Using strace, I see that stty calls ioctl(TIOCGWINSZ) on stdin (fd=0)
> if it failed on stdout (fd=1), whereas Python only tries stdout.
It was done this way by design. Maybe checking stdin can be also useful,
but it is a rather big change in semantics. I think it should be a separate bug.

It is pretty common for programs to behave differently when run through pipe, even if stdin is on a tty. stty is rather the exception than the rule. E.g. almost all programs disable color when piped explicitly through less. 'dpkg | cat' ignores terminal width. So does git and ls.
stty is special, because the only purpose of that program is to query terminal size, but it cannot be taken as a model for the behaviour of a general purpose program.
msg153237 - (view) Author: Arfrever Frehtes Taifersar Arahesis (Arfrever) * (Python triager) Date: 2012-02-12 23:38
The purpose of os.get_terminal_size() is the same as `stty size`, so `stty size` could be a model for behavior of os.get_terminal_size().
msg153238 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2012-02-12 23:45
> E.g. almost all programs disable color when piped explicitly through less.

Using my patch, you can use os.get_terminal_size(sys.stdout.fileno()) if you like to get an error if sys.stdout is a pipe. My patch only changes the behaviour if no argument is specified (hum, the "special" behaviour should be documented). Or you can also check explicitly sys.stdout.isatty().

It is just more convinient to fallback to stdin if stdout is not a TTY.
msg153380 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2012-02-15 00:17
> New try (I fixed my email address and the patch).
>
> The purpose of os.get_terminal_size() is the same as `stty size`, so 
> `stty size` could be a model for behavior of os.get_terminal_size().
I guess that this fallback will make some things easier. As long it is documented, the user should be able to get whatever behaviour they want.

I have four small questions:
- -1 is used as the argument meaning "try stdout and stdin". But -1 is 
  also used the error return value for open(2) and other system 
  functions. I know that Python checks the return value to throw an 
  exception, and -1 shouldn't "leak" and be passed by mistake to 
  os.get_terminal_size(). Can someone confirm that this is
  the case?
- Why stderr is not checked too? It would make os.get_terminal_size()
  return the real terminal value in a the middle of a pipe:
    cat | python -c 'import os; os.get_terminal_size()' | cat.
- some doc change is missing
- in the new version the line with ioctl(fd, TIOCGWINSZ, &w) repeats
  three times. It think it could be reduced to two without loss
  of readability.
msg153382 - (view) Author: Denilson Figueiredo de Sá (denilsonsa) Date: 2012-02-15 01:04
On Tue, Feb 14, 2012 at 22:17, Zbyszek Szmek <report@bugs.python.org> wrote:
>
> I have four small questions:
> - -1 is used as the argument meaning "try stdout and stdin".
> - Why stderr is not checked too?

I propose the following solution: accept either an integer or a
sequence of integers as the fd parameter. In the second case, try each
FD until one of them returns the terminal size.
msg153390 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2012-02-15 06:34
Wouldn't this be quite unwieldy to use:
   os.get_terminal_size(sys.__stdout__.fileno(),
                        sys.__stdin__().fileno(),
                        sys.__stderr__.fileno())
?
msg153494 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2012-02-16 19:14
I don't think there's much point in the proposed complications.
If you're willing to know the terminal size, you're probably interested in displaying something in it (using stdout), so why would you care about stderr or stdin?
msg153495 - (view) Author: Zbyszek Jędrzejewski-Szmek (zbysz) * Date: 2012-02-16 19:25
Stdout can be connected to a pipe, e.g to less, which in turn might
be connected to a terminal. The program can then display output properly 
scaled for the terminal, assuming that because stdin is
connnected to a terminal, output will eventually reach the same 
terminal. Sometimes this is true, sometimes not.
msg160877 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2012-05-16 15:43
I am closing as fixed. If you want to propose further enhancements, please open a new issue.
History
Date User Action Args
2022-04-11 14:57:24adminsetgithub: 57818
2012-05-16 15:43:07pitrousetstatus: open -> closed
resolution: fixed
messages: + msg160877
2012-02-16 19:25:36zbyszsetmessages: + msg153495
2012-02-16 19:14:59pitrousetmessages: + msg153494
2012-02-15 06:34:39zbyszsetmessages: + msg153390
2012-02-15 01:04:45denilsonsasetmessages: + msg153382
2012-02-15 00:17:41zbyszsetmessages: + msg153380
2012-02-12 23:45:10vstinnersetmessages: + msg153238
2012-02-12 23:38:40Arfreversetmessages: + msg153237
2012-02-12 23:28:22zbyszsetmessages: + msg153236
2012-02-12 23:25:24vstinnersetfiles: + get_terminal_size_pipe-2.patch

messages: + msg153235
2012-02-12 23:13:20pitrousetmessages: + msg153233
2012-02-12 23:02:44vstinnersetfiles: + get_terminal_size_pipe.patch

messages: + msg153232
2012-02-12 22:00:18Arfreversetstatus: closed -> open
resolution: fixed -> (no value)
messages: + msg153231
2012-02-12 06:38:21zbyszsetmessages: + msg153185
2012-02-12 06:09:04Arfreversetmessages: + msg153184
2012-02-08 22:59:29zbyszsetmessages: + msg152915
2012-02-08 22:33:52pitrousetstatus: open -> closed
resolution: fixed
messages: + msg152914

stage: patch review -> resolved
2012-02-08 22:31:31python-devsetnosy: + python-dev
messages: + msg152913
2012-02-07 13:09:33vstinnersetmessages: + msg152808
2012-02-07 12:38:10techtoniksetmessages: + msg152807
2012-01-31 16:59:10zbyszsetmessages: + msg152398
2012-01-31 16:53:29techtoniksetmessages: + msg152397
2012-01-31 16:48:32zbyszsetmessages: + msg152396
2012-01-31 16:45:13techtoniksetmessages: + msg152395
2012-01-31 16:36:33zbyszsetfiles: + termsize.diff.8

messages: + msg152394
2012-01-31 15:03:27vstinnersetmessages: + msg152390
2012-01-31 14:31:23pitrousetmessages: + msg152389
2012-01-31 14:22:26techtoniksetnosy: + techtonik
messages: + msg152388
2012-01-30 23:25:44zbyszsetfiles: + termsize.diff.7

messages: + msg152360
2012-01-30 17:24:22vstinnersetmessages: + msg152334
2012-01-30 17:00:32zbyszsetfiles: + termsize.diff.6

messages: + msg152330
2012-01-22 13:09:46neologixsetmessages: + msg151772
2012-01-22 00:20:02denilsonsasetmessages: + msg151755
2012-01-21 22:47:26pitrousetmessages: + msg151749
2012-01-21 19:40:18giampaolo.rodolasetmessages: + msg151742
2012-01-21 19:04:36pitrousetmessages: + msg151740
2012-01-21 13:57:36vstinnersetmessages: + msg151729
2012-01-16 17:03:26zbyszsetmessages: + msg151391
2012-01-07 16:20:12eric.araujosetnosy: + eric.araujo
2012-01-06 23:07:00zbyszsetmessages: + msg150776
2012-01-06 22:44:31vstinnersetmessages: + msg150775
2012-01-06 18:55:09zbyszsetfiles: + termsize.diff.5

messages: + msg150759
2012-01-06 13:44:57amaury.forgeotdarcsetmessages: + msg150732
2012-01-06 13:27:10zbyszsetmessages: + msg150731
2012-01-06 12:30:17zbyszsetfiles: + termsize.diff.4

messages: + msg150722
2012-01-06 06:10:01loewissetmessages: + msg150715
2012-01-06 00:26:47pitrousetmessages: + msg150703
2012-01-06 00:19:29vstinnersetnosy: + vstinner
messages: + msg150701
2012-01-05 21:42:14pitrousetmessages: + msg150695
2012-01-05 21:24:53zbyszsetmessages: + msg150693
2012-01-05 20:54:59loewissetmessages: + msg150689
2012-01-02 22:43:18zbyszsetfiles: + termsize.diff.3

messages: + msg150481
2012-01-02 19:49:26rosslagerwallsetmessages: + msg150475
2012-01-02 18:57:25zbyszsetmessages: + msg150474
2012-01-02 16:46:53rosslagerwallsetmessages: + msg150462
2012-01-02 13:55:33giampaolo.rodolasetmessages: + msg150456
2012-01-02 13:00:27pitrousetnosy: + loewis, neologix, rosslagerwall
messages: + msg150454
2011-12-31 14:01:48zbyszsetfiles: + termsize.diff.2

messages: + msg150419
2011-12-30 20:16:05pitrousetmessages: + msg150388
2011-12-28 21:28:27Arfreversetnosy: + Arfrever
2011-12-28 13:28:00pitrousetstage: needs patch -> patch review
2011-12-28 13:08:18zbyszsetmessages: + msg150287
2011-12-17 01:41:33zbyszsetfiles: + termsize.diff.1

messages: + msg149653
2011-12-16 17:32:03pitrousetmessages: + msg149631
2011-12-16 16:45:15amaury.forgeotdarcsetnosy: + amaury.forgeotdarc
messages: + msg149630
2011-12-16 16:09:28zbyszsetfiles: + termsize.diff
keywords: + patch
messages: + msg149629
2011-12-16 15:50:35giampaolo.rodolasetmessages: + msg149627
2011-12-16 13:41:31zbyszsetmessages: + msg149620
2011-12-16 12:16:47giampaolo.rodolasetmessages: + msg149617
2011-12-16 12:12:27giampaolo.rodolalinkissue13041 dependencies
2011-12-16 12:07:30giampaolo.rodolasetnosy: + giampaolo.rodola
messages: + msg149616
2011-12-16 11:28:36denilsonsasetmessages: + msg149613
2011-12-16 08:14:55zbyszsetnosy: + zbysz
messages: + msg149597
2011-12-16 06:53:05pitrousetversions: + Python 3.3
nosy: + pitrou

messages: + msg149595

stage: needs patch
2011-12-16 01:01:38denilsonsacreate