Skip to content

Commit

Permalink
feat: add wrappers for sys.__std<in/out/err>__ to corgy.types
Browse files Browse the repository at this point in the history
  • Loading branch information
jayanthkoushik committed Feb 3, 2022
1 parent 95bb1a6 commit d5f26f1
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 2 deletions.
55 changes: 53 additions & 2 deletions corgy/types.py
Expand Up @@ -84,7 +84,32 @@ def _get_output_stream(name: StrOrPath) -> FileIO:
raise ArgumentTypeError(f"could not open `{name}`: {e}") from None


class OutputTextFile(TextIOWrapper):
def _get_wrapped_buf(cls, buffer):
"""Return an instance of `cls` wrapping `buffer`."""
# This function is used to implement the `std<in/out/err>_wrapper` class methods
# in `InputTextFile` and `OutputTextFile`.
obj_name = f"__{buffer.name}" # private variable to hold the singleton
obj = getattr(cls, obj_name, None)
if obj is None:
obj = cls.__new__(cls)
super(cls, obj).__init__(buffer, line_buffering=True)
setattr(cls, obj_name, obj)
return obj


class _OutputTextFileMeta(type):
# Python < 3.9 does not support `classmethod` combined with `property`,
# so we need to define class properties as properties on the metaclass.
@property
def stdout_wrapper(cls):
return _get_wrapped_buf(cls, sys.__stdout__.buffer)

@property
def stderr_wrapper(cls):
return _get_wrapped_buf(cls, sys.__stderr__.buffer)


class OutputTextFile(TextIOWrapper, metaclass=_OutputTextFileMeta):
"""`TextIOWrapper` sub-class representing an output file.
Args:
Expand Down Expand Up @@ -113,6 +138,18 @@ def __str__(self) -> str:
def init(self):
"""No-op for compatibility with `LazyOutputTextFile`."""

@classmethod
def stdout_wrapper(cls) -> "OutputTextFile":
"""`sys.__stdout__` wrapped with `TextIOWrapper` (line buffered)."""
# For sphinx.
...

@classmethod
def stderr_wrapper(cls) -> "OutputTextFile":
"""`sys.__stderr__` wrapped with `TextIOWrapper` (line buffered)."""
# For sphinx.
...


class LazyOutputTextFile(OutputTextFile):
"""`OutputTextFile` sub-class that does not auto-initialize.
Expand Down Expand Up @@ -180,7 +217,15 @@ def init(self):
super().__init__(self._path)


class InputTextFile(TextIOWrapper):
class _InputTextFileMeta(type):
# Python < 3.9 does not support `classmethod` combined with `property`,
# so we need to define class properties as properties on the metaclass.
@property
def stdin_wrapper(cls):
return _get_wrapped_buf(cls, sys.__stdin__.buffer)


class InputTextFile(TextIOWrapper, metaclass=_InputTextFileMeta):
"""`TextIOWrapper` sub-class representing an input file.
Args:
Expand Down Expand Up @@ -208,6 +253,12 @@ def __repr__(self) -> str:
def __str__(self) -> str:
return str(self.buffer.name)

@classmethod
def stdin_wrapper(cls) -> "InputTextFile":
"""`sys.__stdin__` wrapped with `TextIOWrapper`."""
# For sphinx.
...


class InputBinFile(BufferedReader):
"""Type for an input binary file.
Expand Down
6 changes: 6 additions & 0 deletions docs/corgy.types.md
Expand Up @@ -50,6 +50,10 @@ is raised if any of the operations fail.
No-op for compatibility with `LazyOutputTextFile`.


#### stdout_wrapper(_ = OutputTextFile('<stdout>'_ )

#### stderr_wrapper(_ = OutputTextFile('<stderr>'_ )

### _class_ corgy.types.OutputBinFile(path)
Type for an output binary file.

Expand Down Expand Up @@ -108,6 +112,8 @@ The file must exist, and will be opened in text mode (`r`). `ArgumentTypeError`
raised if this fails.


#### stdin_wrapper(_ = InputTextFile('<stdin>'_ )

### _class_ corgy.types.InputBinFile(path)
Type for an input binary file.

Expand Down
20 changes: 20 additions & 0 deletions tests/test_types.py
Expand Up @@ -82,6 +82,20 @@ def test_output_text_file_type(self):
with self.type(os.path.join(self.tmp_dir.name, "foo.txt")) as f:
self.assertIsInstance(f, TextIOWrapper)

def test_output_text_file_stdouterr_wrappers(self):
for wrapper, buffer in zip(
[OutputTextFile.stdout_wrapper, OutputTextFile.stderr_wrapper],
[sys.__stdout__.buffer, sys.__stderr__.buffer],
):
with self.subTest(wrapper=wrapper):
self.assertIsInstance(wrapper, OutputTextFile)
self.assertIs(wrapper.buffer, buffer)
if buffer is sys.__stdout__.buffer:
# Singleton test.
self.assertIs(OutputTextFile.stdout_wrapper, wrapper)
else:
self.assertIs(OutputTextFile.stderr_wrapper, wrapper)


class TestOutputBinFile(_TestOutputFile):
type = OutputBinFile
Expand Down Expand Up @@ -152,6 +166,12 @@ def test_input_text_file_type(self):
with self.type(self.tmp_file_name) as f:
self.assertIsInstance(f, TextIOWrapper)

def test_input_text_file_stdin_wrapper(self):
wrapper = InputTextFile.stdin_wrapper
self.assertIsInstance(wrapper, InputTextFile)
self.assertIs(wrapper.buffer, sys.__stdin__.buffer)
self.assertIs(InputTextFile.stdin_wrapper, wrapper) # singleton test


class TestInputBinFile(_TestInputFile):
type = InputBinFile
Expand Down

0 comments on commit d5f26f1

Please sign in to comment.