summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--click.py22
-rw-r--r--docs/index.rst1
-rw-r--r--docs/python3.rst89
3 files changed, 108 insertions, 4 deletions
diff --git a/click.py b/click.py
index a186e7a..fa617b8 100644
--- a/click.py
+++ b/click.py
@@ -48,26 +48,39 @@ if PY2:
f = _wrap_stream_for_codec(f, encoding, errors)
return f, False
else:
+ import io
text_type = str
raw_input = input
+ def _make_binary_stream(f, stream_name):
+ buf = getattr(f, 'buffer', None)
+ if buf is not None and isinstance(buf, io.BufferedReader):
+ return buf
+ raise TypeError('%s is a stream that does not give access to its '
+ 'underlying binary stream. Please do not set '
+ 'sys.stdout directly to a StringIO object or '
+ 'something similar.' % stream_name)
+
def open_stream(filename, mode='r', encoding=None, errors='strict'):
if filename != '-':
if encoding is not None:
- return open(filename, mode, encoding=encoding, errors=errors), True
+ return open(filename, mode, encoding=encoding,
+ errors=errors), True
return open(filename, mode), True
+
if 'w' in mode:
f = sys.stdout
if encoding is not None or 'b' in mode:
- f = f.buffer.raw
+ f = _make_binary_stream(f, 'stdout')
if encoding is not None:
f = _wrap_stream_for_codec(f, encoding, errors)
else:
f = sys.stdin
if 'b' in mode:
- f = f.buffer.raw
+ f = _make_binary_stream(f, 'stdin')
if encoding is not None:
f = _wrap_stream_for_codec(f, encoding, errors)
+
return f, False
@@ -85,7 +98,8 @@ def get_terminal_size():
try:
import fcntl
import termios
- cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))
+ cr = struct.unpack(
+ 'hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))
except Exception:
return
return cr
diff --git a/docs/index.rst b/docs/index.rst
index 738ba2c..13ed17c 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -56,6 +56,7 @@ of the library.
setuptools
complex
advanced
+ python3
API Reference
-------------
diff --git a/docs/python3.rst b/docs/python3.rst
new file mode 100644
index 0000000..a9e8933
--- /dev/null
+++ b/docs/python3.rst
@@ -0,0 +1,89 @@
+Python 3 Support
+================
+
+.. currentmodule:: click
+
+Click supports Python 3 but like all other command line utility libraries,
+click suffers from the Unicode text model in Python 3. All examples in
+the documentation were written so that they run on both Python 2.x and
+Python 3.3 or higher.
+
+At the moment the strong recommendation is to use Python 2 for these
+utilities unless you can avoid it.
+
+.. _python3-limitations:
+
+Python 3 Limitations
+--------------------
+
+At the moment click suffers from a few problems on Python 3:
+
+* The command line in Unix traditionally is in bytes and not unicode.
+ While there are encoding hits for all of this, there are generally
+ some situations where this can break. The most common one is SSH
+ connections to machines with different locale.
+
+ As such in misconfigured environments on Python 3 currently can cause
+ a wide range of unicode problems caused by lack of support for
+ roundtripping surrogate escapes. This will be fixed on a case by case
+ basis going forward.
+
+* Standard input and output on Python 3 is opened in unicode mode by
+ default. Click has to reopen the stream in binary mode in certain
+ situations. Because there is no standardized way to do this, this
+ might not always work. Primarily this can become a problem in when
+ testing command line applications.
+
+ This is not supported::
+
+ sys.stdin = io.StringIO('Input here')
+ sys.stdout = io.StringIO()
+
+ Instead you need to do this::
+
+ input = 'Input here'
+ in_stream = io.BytesIO(input.encode('utf-8'))
+ sys.stdin = io.TextIOWrapper(stream, encoding='utf-8')
+ out_stream = io.BytesIO()
+ sys.stdout = io.TextIOWrapper(out_stream, encoding='utf-8')
+
+ Remember that in that case you ened to use ``out_stream.getvalue()``
+ and not ``sys.stdout.getvalue()`` if you want to access the buffer
+ contents as the wrapper will not forward that method.
+
+Python 2 / 3 Differences
+------------------------
+
+Click attempts to minimize the differences between Python 2 and Python 3
+by following the best practices for both languages.
+
+On Python 2 the following is true:
+
+* ``sys.stdin``, ``sys.stdout``, and ``sys.stderr`` are opened in binary
+ mode but under some circumstances support unicode output. Click
+ attempts to not subvert this but provides support for forcing streams
+ to be unicode based.
+* ``sys.argv`` is always bytes based. Click will pass bytes to all
+ input types and convert as necessary. The :class:`STRING` type
+ automatically will decode properly the input value into a string by
+ trying the most appropriate encodings.
+* When dealing with files, click will never go through the unicode APIs
+ and instead use the operating system's byte APIs to open the files.
+
+On Python 3 the following is true:
+
+* ``sys.stdin``, ``sys.stdout`` and ``sys.stderr`` are by text based.
+ When click needs a binary stream it attempts to discover the
+ nderlying binary stream. See :ref:`python3-limitations` for how this
+ works.
+* ``sys.argv`` is always unicode based. This also means that the native
+ type for input values to the types in click is unicode and not bytes.
+
+ This causes problems when the terminal is incorrectly set and Python
+ does not figure out the encoding. In that case the unicode string
+ will contain error bytes encoded as surrogate escapes.
+* When dealing with files click will always use the unicode file system
+ API calls by using the operating system's reported or guessed
+ filesystem encoding. Surrogates are supported for filenames so it
+ should be possible to open files through the :class:`File` type even
+ if the environment is misconfigured.