SF.net SVN: docutils:[9817 ] trunk/docutils/docutils/i o.py
aa-turner--- via Docutils-checkins <[email protected]>
| Newsgroups | gmane.text.docutils.cvs |
|---|---|
| Message-ID | <[email protected]> |
Revision: 9817
http://sourceforge.net/p/docutils/code/9817
Author: aa-turner
Date: 2024-08-01 17:24:14 +0000 (Thu, 01 Aug 2024)
Log Message:
-----------
Add type hints to ``docutils.io``
Modified Paths:
--------------
trunk/docutils/docutils/io.py
Modified: trunk/docutils/docutils/io.py
===================================================================
--- trunk/docutils/docutils/io.py 2024-08-01 14:27:54 UTC (rev 9816)
+++ trunk/docutils/docutils/io.py 2024-08-01 17:24:14 UTC (rev 9817)
@@ -7,6 +7,8 @@
exist for a variety of input/output mechanisms.
"""
+from __future__ import annotations
+
__docformat__ = 'reStructuredText'
import codecs
@@ -15,10 +17,15 @@
import re
import sys
import warnings
+from typing import TYPE_CHECKING
from docutils import TransformSpec
+if TYPE_CHECKING:
+ from typing import Any, BinaryIO, ClassVar, Final, Literal, TextIO
+ from docutils import nodes
+
# Guess the locale's preferred encoding.
# If no valid guess can be made, _locale_encoding is set to `None`:
#
@@ -29,9 +36,9 @@
# Return locale encoding also in UTF-8 mode
with warnings.catch_warnings():
warnings.simplefilter("ignore")
- _locale_encoding = (locale.getlocale()[1]
- or locale.getdefaultlocale()[1])
- _locale_encoding = _locale_encoding.lower()
+ _locale_encoding: str | None = (
+ locale.getlocale()[1] or locale.getdefaultlocale()[1]
+ ).lower()
except: # noqa any other problems determining the locale -> use None
_locale_encoding = None
try:
@@ -44,7 +51,7 @@
class OutputError(OSError): pass
-def check_encoding(stream, encoding):
+def check_encoding(stream: TextIO, encoding: str) -> bool | None:
"""Test, whether the encoding of `stream` matches `encoding`.
Returns
@@ -60,7 +67,7 @@
return None
-def error_string(err) -> str:
+def error_string(err: BaseException) -> str:
"""Return string representation of Exception `err`.
"""
return f'{err.__class__.__name__}: {err}'
@@ -78,12 +85,17 @@
(Optional for custom input objects since Docutils 0.19.)
"""
- component_type = 'input'
+ component_type: Final = 'input'
- default_source_path = None
+ default_source_path: ClassVar[str | None] = None
- def __init__(self, source=None, source_path=None, encoding='utf-8',
- error_handler='strict') -> None:
+ def __init__(
+ self,
+ source: str | TextIO | nodes.document | None = None,
+ source_path: str | os.PathLike[str] | None = None,
+ encoding: str | Literal['unicode'] | None = 'utf-8',
+ error_handler: str | None = 'strict',
+ ) -> None:
self.encoding = encoding
"""Text encoding for the input source."""
@@ -106,11 +118,11 @@
return '%s: source=%r, source_path=%r' % (self.__class__, self.source,
self.source_path)
- def read(self):
+ def read(self) -> str:
"""Return input as `str`. Define in subclasses."""
raise NotImplementedError
- def decode(self, data):
+ def decode(self, data: str | bytes) -> str:
"""
Decode `data` if required.
@@ -164,20 +176,23 @@
f'{", ".join(repr(enc) for enc in encoding_candidates)}.\n'
f'({error_string(error)})')
- coding_slug = re.compile(br"coding[:=]\s*([-\w.]+)")
+ coding_slug: ClassVar[re.Pattern[bytes]] = re.compile(
+ br'coding[:=]\s*([-\w.]+)'
+ )
"""Encoding declaration pattern."""
- byte_order_marks = ((codecs.BOM_UTF32_BE, 'utf-32'),
- (codecs.BOM_UTF32_LE, 'utf-32'),
- (codecs.BOM_UTF8, 'utf-8-sig'),
- (codecs.BOM_UTF16_BE, 'utf-16'),
- (codecs.BOM_UTF16_LE, 'utf-16'),
- )
+ byte_order_marks: ClassVar[tuple[tuple[bytes, str], ...]] = (
+ (codecs.BOM_UTF32_BE, 'utf-32'),
+ (codecs.BOM_UTF32_LE, 'utf-32'),
+ (codecs.BOM_UTF8, 'utf-8-sig'),
+ (codecs.BOM_UTF16_BE, 'utf-16'),
+ (codecs.BOM_UTF16_LE, 'utf-16'),
+ )
"""Sequence of (start_bytes, encoding) tuples for encoding detection.
The first bytes of input data are checked against the start_bytes strings.
A match indicates the given encoding."""
- def determine_encoding_from_data(self, data):
+ def determine_encoding_from_data(self, data: bytes) -> str | None:
"""
Try to determine the encoding of `data` by looking *in* `data`.
Check for a byte order mark (BOM) or an encoding declaration.
@@ -193,7 +208,7 @@
return match.group(1).decode('ascii')
return None
- def isatty(self):
+ def isatty(self) -> bool:
"""Return True, if the input source is connected to a TTY device."""
try:
return self.source.isatty()
@@ -213,22 +228,27 @@
(Optional for custom output objects since Docutils 0.19.)
"""
- component_type = 'output'
+ component_type: Final = 'output'
- default_destination_path = None
+ default_destination_path: ClassVar[str | None] = None
- def __init__(self, destination=None, destination_path=None,
- encoding=None, error_handler='strict') -> None:
- self.encoding = encoding
+ def __init__(
+ self,
+ destination: TextIO | str | bytes | None = None,
+ destination_path: str | os.PathLike[str] | None = None,
+ encoding: str | None = None,
+ error_handler: str | None = 'strict',
+ ) -> None:
+ self.encoding: str | None = encoding
"""Text encoding for the output destination."""
- self.error_handler = error_handler or 'strict'
+ self.error_handler: str = error_handler or 'strict'
"""Text encoding error handler."""
- self.destination = destination
+ self.destination: TextIO | str | bytes | None = destination
"""The destination for output data."""
- self.destination_path = destination_path
+ self.destination_path: str | os.PathLike[str] | None = destination_path
"""A text reference to the destination."""
if not destination_path:
@@ -238,11 +258,11 @@
return ('%s: destination=%r, destination_path=%r'
% (self.__class__, self.destination, self.destination_path))
- def write(self, data):
+ def write(self, data: str | bytes) -> str | bytes | None:
"""Write `data`. Define in subclasses."""
raise NotImplementedError
- def encode(self, data):
+ def encode(self, data: str | bytes) -> str | bytes:
"""
Encode and return `data`.
@@ -266,13 +286,16 @@
class ErrorOutput:
"""
Wrapper class for file-like error streams with
- failsafe de- and encoding of `str`, `bytes`, `unicode` and
- `Exception` instances.
+ failsafe de- and encoding of `str`, `bytes`, and `Exception` instances.
"""
- def __init__(self, destination=None, encoding=None,
- encoding_errors='backslashreplace',
- decoding_errors='replace') -> None:
+ def __init__(
+ self,
+ destination: TextIO | BinaryIO | str | Literal[False] | None = None,
+ encoding: str | None = None,
+ encoding_errors: str = 'backslashreplace',
+ decoding_errors: str = 'replace',
+ ) -> None:
"""
:Parameters:
- `destination`: a file-like object,
@@ -290,20 +313,24 @@
elif isinstance(destination, str):
destination = open(destination, 'w')
- self.destination = destination
+ self.destination: TextIO | BinaryIO | Literal[False] = destination
"""Where warning output is sent."""
- self.encoding = (encoding or getattr(destination, 'encoding', None)
- or _locale_encoding or 'ascii')
+ self.encoding: str = (
+ encoding
+ or getattr(destination, 'encoding', None)
+ or _locale_encoding
+ or 'ascii'
+ )
"""The output character encoding."""
- self.encoding_errors = encoding_errors
+ self.encoding_errors: str = encoding_errors
"""Encoding error handler."""
- self.decoding_errors = decoding_errors
+ self.decoding_errors: str = decoding_errors
"""Decoding error handler."""
- def write(self, data) -> None:
+ def write(self, data: str | bytes | Exception) -> None:
"""
Write `data` to self.destination. Ignore, if self.destination is False.
@@ -313,21 +340,27 @@
return
if isinstance(data, Exception):
data = str(data)
+ # The destination is either opened in text or binary mode.
+ # If data has the wrong type, try to convert it.
try:
self.destination.write(data)
except UnicodeEncodeError:
- self.destination.write(data.encode(self.encoding,
- self.encoding_errors))
+ # Encoding data from string to bytes failed with the
+ # destination's encoding and error handler.
+ # Try again with our own encoding and error handler.
+ binary = data.encode(self.encoding, self.encoding_errors)
+ self.destination.write(binary)
except TypeError:
if isinstance(data, str): # destination may expect bytes
- self.destination.write(data.encode(self.encoding,
- self.encoding_errors))
+ binary = data.encode(self.encoding, self.encoding_errors)
+ self.destination.write(binary)
elif self.destination in (sys.stderr, sys.stdout):
# write bytes to raw stream
self.destination.buffer.write(data)
else:
- self.destination.write(str(data, self.encoding,
- self.decoding_errors))
+ # destination in text mode, write str
+ string = data.decode(self.encoding, self.decoding_errors)
+ self.destination.write(string)
def close(self) -> None:
"""
@@ -343,7 +376,7 @@
except AttributeError:
pass
- def isatty(self):
+ def isatty(self) -> bool:
"""Return True, if the destination is connected to a TTY device."""
try:
return self.destination.isatty()
@@ -356,9 +389,15 @@
"""
Input for single, simple file-like objects.
"""
- def __init__(self, source=None, source_path=None,
- encoding='utf-8', error_handler='strict',
- autoclose=True, mode='r') -> None:
+ def __init__(
+ self,
+ source: TextIO | None = None,
+ source_path: str | os.PathLike[str] | None = None,
+ encoding: str | Literal['unicode'] | None = 'utf-8',
+ error_handler: str | None = 'strict',
+ autoclose: bool = True,
+ mode: Literal['r', 'rb', 'br'] = 'r'
+ ) -> None:
"""
:Parameters:
- `source`: either a file-like object (which is read directly), or
@@ -371,7 +410,7 @@
- `mode`: how the file is to be opened (see standard function
`open`). The default is read only ('r').
"""
- Input.__init__(self, source, source_path, encoding, error_handler)
+ super().__init__(source, source_path, encoding, error_handler)
self.autoclose = autoclose
self._stderr = ErrorOutput()
@@ -396,7 +435,7 @@
except AttributeError:
pass
- def read(self):
+ def read(self) -> str:
"""
Read and decode a single file, return as `str`.
"""
@@ -415,7 +454,7 @@
self.close()
return data
- def readlines(self):
+ def readlines(self) -> list[str]:
"""
Return lines of a single file as list of strings.
"""
@@ -430,17 +469,24 @@
"""Output for single, simple file-like objects."""
- default_destination_path = '<file>'
+ default_destination_path: Final = '<file>'
- mode = 'w'
+ mode: Literal['w', 'a', 'x', 'wb', 'ab', 'xb', 'bw', 'ba', 'bx'] = 'w'
"""The mode argument for `open()`."""
# 'wb' for binary (e.g. OpenOffice) files (see also `BinaryFileOutput`).
# (Do not use binary mode ('wb') for text files, as this prevents the
# conversion of newlines to the system specific default.)
- def __init__(self, destination=None, destination_path=None,
- encoding=None, error_handler='strict', autoclose=True,
- handle_io_errors=None, mode=None) -> None:
+ def __init__(
+ self,
+ destination: TextIO | None = None,
+ destination_path: str | os.PathLike[str] | None = None,
+ encoding: str | None = None,
+ error_handler: str | None = 'strict',
+ autoclose: bool = True,
+ handle_io_errors: None = None,
+ mode=None,
+ ) -> None:
"""
:Parameters:
- `destination`: either a file-like object (which is written
@@ -457,8 +503,9 @@
`open`). The default is 'w', providing universal newline
support for text files.
"""
- Output.__init__(self, destination, destination_path,
- encoding, error_handler)
+ super().__init__(
+ destination, destination_path, encoding, error_handler,
+ )
self.opened = True
self.autoclose = autoclose
if handle_io_errors is not None:
@@ -485,7 +532,7 @@
except AttributeError:
pass
- def open(self):
+ def open(self) -> None:
# Specify encoding
if 'b' not in self.mode:
kwargs = {'encoding': self.encoding,
@@ -499,7 +546,7 @@
self.destination_path)
self.opened = True
- def write(self, data):
+ def write(self, data: str | bytes) -> str | bytes:
"""Write `data` to a single file, also return it.
`data` can be a `str` or `bytes` instance.
@@ -561,7 +608,7 @@
# Used by core.publish_cmdline_to_binary() which is also deprecated.
mode = 'wb'
- def __init__(self, *args, **kwargs) -> None:
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
warnings.warn('"BinaryFileOutput" is obsoleted by "FileOutput"'
' and will be removed in Docutils 0.24.',
DeprecationWarning, stacklevel=2)
@@ -571,9 +618,11 @@
class StringInput(Input):
"""Input from a `str` or `bytes` instance."""
- default_source_path = '<string>'
+ source: str | bytes
- def read(self):
+ default_source_path: Final = '<string>'
+
+ def read(self) -> str:
"""Return the source as `str` instance.
Decode, if required (see `Input.decode`).
@@ -587,9 +636,11 @@
Provisional.
"""
- default_destination_path = '<string>'
+ destination: str | bytes
- def write(self, data):
+ default_destination_path: Final = '<string>'
+
+ def write(self, data: str | bytes) -> str | bytes:
"""Store `data` in `self.destination`, and return it.
If `self.encoding` is set to the pseudo encoding name "unicode",
@@ -612,8 +663,10 @@
"""Degenerate input: read nothing."""
- default_source_path = 'null input'
+ source: None
+ default_source_path: Final = 'null input'
+
def read(self) -> str:
"""Return an empty string."""
return ''
@@ -623,9 +676,11 @@
"""Degenerate output: write nothing."""
- default_destination_path = 'null output'
+ destination: None
- def write(self, data) -> None:
+ default_destination_path: Final = 'null output'
+
+ def write(self, data: str | bytes) -> None:
"""Do nothing, return None."""
pass
@@ -638,8 +693,10 @@
The document tree must be passed in the ``source`` parameter.
"""
- default_source_path = 'doctree input'
+ source: nodes.document
- def read(self):
+ default_source_path: Final = 'doctree input'
+
+ def read(self) -> nodes.document:
"""Return the document tree."""
return self.source
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.