Skip to main content
added 168 characters in body
Source Link
Reinderien
  • 71.1k
  • 5
  • 76
  • 256
  • Avoid representing internal data as dictionaries if possible. As @RootTwo indicates, it's trivial to initialize a dataclass from a dict via the ** kwarg-splatting operator if that's needed.
  • Would be nice to support output to string, stdout or a file through wrappers. StringIO makes this easy.
  • Avoid \t - it has medium-dependent indentation; better to have space rendering with a user-configurable indentation level.
  • You should wrap everything, not just your description.
  • This is a matter of opinion, but I disagree with double-newlines on the inside of your sections; reserving double-newlines for section breaks makes things clearer.
from dataclasses import dataclass
from io import StringIO
from sys import stdout
from textwrap import TextWrapper
from typing import Optional, Tuple, Sequence, TextIO, Iterable

Pair = Tuple[str, str]
PairSeq = Sequence[Pair]


@dataclass
class Documentation:
    summary: Optional[str] = None
    args: Optional[PairSeq] = None
    attrs: Optional[PairSeq] = None
    raises: Optional[PairSeq] = None
    returns: Optional[str] = None
    desc: Optional[str] = None
    todo: Optional[Sequence[str]] = None
    indent: int = 4
    wrap: int = 80

    def __post_init__(self):
        self._outer_wrapperwrap_outer = TextWrapper(width=self.wrap).wrapfill
        indent = ' ' * self.indent
        self._inner_wrapperwrap_inner = TextWrapper(
            width=self.wrap,
            initial_indent=indent,
            subsequent_indent=indent,
        ).wrap

    def inner_wrap(self, t: str) -> Iterable[str]:
        for line in self._inner_wrapper(t):
            yield line + '\n'

    def outer_wrap(self, t: str) -> Iterable[str]:
        for line in self._outer_wrapper(t):
            yield line + '\n'fill

    def __str__(self) -> str:
        with StringIO() as f:
            self.to_file(f)
            return f.getvalue()

    def print(self) -> None:
        self.to_file(stdout)

    def write_section(self, title: str, content: str, f: TextIO) -> None:
        if content:
            f.write(f'\n{title}:\n')
            f.writelineswrite(self.inner_wrapwrap_inner(content) + '\n')

    def write_pairs(self, title: str, pairs: PairSeq, f: TextIO) -> None:
        if pairs:
            f.write(f'\n{title}:\n')
            f.writelines(
                line
                for self.wrap_inner(f'{name,}: {desc}') in+ pairs'\n'
                for line in self.inner_wrap(f'{name}:, {desc}') in pairs
            )

    def to_file(self, f: TextIO) -> None:
        if self.summary:
            f.writelineswrite(self.outer_wrapwrap_outer(self.summary))

        self.write_pairs('Arguments', self.args, f)
        self.write_pairs('Attributes', self.attrs, f)
        self.write_pairs('Raises', self.raises, f)
        self.write_section('Returns', self.returns, f)
        self.write_section('Description', self.desc, f)

        if self.todo:
            f.write(f'\nTodo:\n')
            f.writelines(
                line
                forself.wrap_inner(f'* {todo}') in+ self.todo'\n'
                for linetodo in self.inner_wrap(f'* {todo}')
            )


def test():
    Documentation(
        summary='The Pear object describes the properties of pears.',
        args=(
            ('a', 'Function argument'),
            ('b', 'Function argument'),
        ),
        attrs=(
            ('a', 'Information about parameter a'),
            ('b',
             'Information about parameter b'b. '
             'Information about parameter b. '
             'Information about parameter b. '
             'Information about parameter b. '
             ),
        ),
        raises=(
            ('AttributeError',
             'The ``Raises`` section is a list of all '
exceptions that are '
          'exceptions that are relevant'relevant to the interface.'),
        ),
        returns='The return value. True for success, False otherwise.',
        desc='The description may span multiple lines. Following lines should be'
             'be indented. The type is optional. The'
 description may '
        'span  'The description may span multiple lines. Following lines should be'
             'be indented. The type is optional.The '
             'The description may span multiple lines. Following lines should '
        'Following lines should be  'be indented. The type is optional.The '
             'The description may span multiple lines. Following lines should '
        'should be    'be indented. The type is optional.',
        todo=('Do Something', 'Something else'),
    ).print()


if __name__ == '__main__':
    test()
The Pear object describes the properties of pears.
 
Arguments:
    a: Function argument
    b: Function argument

Attributes:
    a: Information about parameter a
    b: Information about parameter b. Information about parameter b. Information
    about parameter b. Information about parameter b.

Raises:
    AttributeError: The ``Raises`` section is a list of all exceptions that are
    relevant to the interface.

Returns:
    The return value. True for success, False otherwise.

Description:
    The description may span multiple lines. Following lines should be indented.
    The type is optional. The description may span multiple lines. Following
    lines should be indented. The type is optional. The description may span
    multiple lines. Following lines should be indented. The type is optional.The
    The description may span multiple lines. Following lines should be indented. The
    The type is optional.

Todo:
    * Do Something
    * Something else
  • Avoid representing internal data as dictionaries if possible.
  • Would be nice to support output to string, stdout or a file through wrappers. StringIO makes this easy.
  • Avoid \t - it has medium-dependent indentation; better to have space rendering with a user-configurable indentation level.
  • You should wrap everything, not just your description.
  • This is a matter of opinion, but I disagree with double-newlines on the inside of your sections; reserving double-newlines for section breaks makes things clearer.
from dataclasses import dataclass
from io import StringIO
from sys import stdout
from textwrap import TextWrapper
from typing import Optional, Tuple, Sequence, TextIO, Iterable

Pair = Tuple[str, str]
PairSeq = Sequence[Pair]


@dataclass
class Documentation:
    summary: Optional[str] = None
    args: Optional[PairSeq] = None
    attrs: Optional[PairSeq] = None
    raises: Optional[PairSeq] = None
    returns: Optional[str] = None
    desc: Optional[str] = None
    todo: Optional[Sequence[str]] = None
    indent: int = 4
    wrap: int = 80

    def __post_init__(self):
        self._outer_wrapper = TextWrapper(width=self.wrap).wrap
        indent = ' ' * self.indent
        self._inner_wrapper = TextWrapper(
            width=self.wrap,
            initial_indent=indent,
            subsequent_indent=indent,
        ).wrap

    def inner_wrap(self, t: str) -> Iterable[str]:
        for line in self._inner_wrapper(t):
            yield line + '\n'

    def outer_wrap(self, t: str) -> Iterable[str]:
        for line in self._outer_wrapper(t):
            yield line + '\n'

    def __str__(self) -> str:
        with StringIO() as f:
            self.to_file(f)
            return f.getvalue()

    def print(self) -> None:
        self.to_file(stdout)

    def write_section(self, title: str, content: str, f: TextIO) -> None:
        if content:
            f.write(f'\n{title}:\n')
            f.writelines(self.inner_wrap(content))

    def write_pairs(self, title: str, pairs: PairSeq, f: TextIO) -> None:
        if pairs:
            f.write(f'\n{title}:\n')
            f.writelines(
                line
                for name, desc in pairs
                for line in self.inner_wrap(f'{name}: {desc}')
            )

    def to_file(self, f: TextIO) -> None:
        if self.summary:
            f.writelines(self.outer_wrap(self.summary))

        self.write_pairs('Arguments', self.args, f)
        self.write_pairs('Attributes', self.attrs, f)
        self.write_pairs('Raises', self.raises, f)
        self.write_section('Returns', self.returns, f)
        self.write_section('Description', self.desc, f)

        if self.todo:
            f.write(f'\nTodo:\n')
            f.writelines(
                line
                for todo in self.todo
                for line in self.inner_wrap(f'* {todo}')
            )


def test():
    Documentation(
        summary='The Pear object describes the properties of pears.',
        args=(
            ('a', 'Function argument'),
            ('b', 'Function argument'),
        ),
        attrs=(
            ('a', 'Information about parameter a'),
            ('b', 'Information about parameter b'),
        ),
        raises=(
            ('AttributeError', 'The ``Raises`` section is a list of all '
             'exceptions that are relevant to the interface.'),
        ),
        returns='The return value. True for success, False otherwise.',
        desc='The description may span multiple lines. Following lines should be indented. The type is optional. The description may '
        'span multiple lines. Following lines should be indented. The type is optional.The description may span multiple lines. '
        'Following lines should be indented. The type is optional.The description may span multiple lines. Following lines '
        'should be indented. The type is optional.',
        todo=('Do Something', 'Something else'),
    ).print()


if __name__ == '__main__':
    test()
The Pear object describes the properties of pears.
 
Arguments:
    a: Function argument
    b: Function argument

Attributes:
    a: Information about parameter a
    b: Information about parameter b

Raises:
    AttributeError: The ``Raises`` section is a list of all exceptions that are
    relevant to the interface.

Returns:
    The return value. True for success, False otherwise.

Description:
    The description may span multiple lines. Following lines should be indented.
    The type is optional. The description may span multiple lines. Following
    lines should be indented. The type is optional.The description may span
    multiple lines. Following lines should be indented. The type is optional.The
    description may span multiple lines. Following lines should be indented. The
    type is optional.

Todo:
    * Do Something
    * Something else
  • Avoid representing internal data as dictionaries if possible. As @RootTwo indicates, it's trivial to initialize a dataclass from a dict via the ** kwarg-splatting operator if that's needed.
  • Would be nice to support output to string, stdout or a file through wrappers. StringIO makes this easy.
  • Avoid \t - it has medium-dependent indentation; better to have space rendering with a user-configurable indentation level.
  • You should wrap everything, not just your description.
  • This is a matter of opinion, but I disagree with double-newlines on the inside of your sections; reserving double-newlines for section breaks makes things clearer.
from dataclasses import dataclass
from io import StringIO
from sys import stdout
from textwrap import TextWrapper
from typing import Optional, Tuple, Sequence, TextIO

Pair = Tuple[str, str]
PairSeq = Sequence[Pair]


@dataclass
class Documentation:
    summary: Optional[str] = None
    args: Optional[PairSeq] = None
    attrs: Optional[PairSeq] = None
    raises: Optional[PairSeq] = None
    returns: Optional[str] = None
    desc: Optional[str] = None
    todo: Optional[Sequence[str]] = None
    indent: int = 4
    wrap: int = 80

    def __post_init__(self):
        self.wrap_outer = TextWrapper(width=self.wrap).fill
        indent = ' ' * self.indent
        self.wrap_inner = TextWrapper(
            width=self.wrap,
            initial_indent=indent,
            subsequent_indent=indent,
        ).fill

    def __str__(self) -> str:
        with StringIO() as f:
            self.to_file(f)
            return f.getvalue()

    def print(self) -> None:
        self.to_file(stdout)

    def write_section(self, title: str, content: str, f: TextIO) -> None:
        if content:
            f.write(f'\n{title}:\n')
            f.write(self.wrap_inner(content) + '\n')

    def write_pairs(self, title: str, pairs: PairSeq, f: TextIO) -> None:
        if pairs:
            f.write(f'\n{title}:\n')
            f.writelines(
                self.wrap_inner(f'{name}: {desc}') + '\n'
                for name, desc in pairs
            )

    def to_file(self, f: TextIO) -> None:
        if self.summary:
            f.write(self.wrap_outer(self.summary))

        self.write_pairs('Arguments', self.args, f)
        self.write_pairs('Attributes', self.attrs, f)
        self.write_pairs('Raises', self.raises, f)
        self.write_section('Returns', self.returns, f)
        self.write_section('Description', self.desc, f)

        if self.todo:
            f.write(f'\nTodo:\n')
            f.writelines(
                self.wrap_inner(f'* {todo}') + '\n'
                for todo in self.todo
            )


def test():
    Documentation(
        summary='The Pear object describes the properties of pears.',
        args=(
            ('a', 'Function argument'),
            ('b', 'Function argument'),
        ),
        attrs=(
            ('a', 'Information about parameter a'),
            ('b',
             'Information about parameter b. '
             'Information about parameter b. '
             'Information about parameter b. '
             'Information about parameter b. '
             ),
        ),
        raises=(
            ('AttributeError',
             'The ``Raises`` section is a list of all exceptions that are '
             'relevant to the interface.'),
        ),
        returns='The return value. True for success, False otherwise.',
        desc='The description may span multiple lines. Following lines should '
             'be indented. The type is optional. '
             'The description may span multiple lines. Following lines should '
             'be indented. The type is optional. '
             'The description may span multiple lines. Following lines should '
             'be indented. The type is optional. '
             'The description may span multiple lines. Following lines should '
             'be indented. The type is optional.',
        todo=('Do Something', 'Something else'),
    ).print()


if __name__ == '__main__':
    test()
The Pear object describes the properties of pears.
Arguments:
    a: Function argument
    b: Function argument

Attributes:
    a: Information about parameter a
    b: Information about parameter b. Information about parameter b. Information
    about parameter b. Information about parameter b.

Raises:
    AttributeError: The ``Raises`` section is a list of all exceptions that are
    relevant to the interface.

Returns:
    The return value. True for success, False otherwise.

Description:
    The description may span multiple lines. Following lines should be indented.
    The type is optional. The description may span multiple lines. Following
    lines should be indented. The type is optional. The description may span
    multiple lines. Following lines should be indented. The type is optional.
    The description may span multiple lines. Following lines should be indented.
    The type is optional.

Todo:
    * Do Something
    * Something else
Source Link
Reinderien
  • 71.1k
  • 5
  • 76
  • 256

  • Avoid representing internal data as dictionaries if possible.
  • Would be nice to support output to string, stdout or a file through wrappers. StringIO makes this easy.
  • Avoid \t - it has medium-dependent indentation; better to have space rendering with a user-configurable indentation level.
  • You should wrap everything, not just your description.
  • This is a matter of opinion, but I disagree with double-newlines on the inside of your sections; reserving double-newlines for section breaks makes things clearer.

Example code

from dataclasses import dataclass
from io import StringIO
from sys import stdout
from textwrap import TextWrapper
from typing import Optional, Tuple, Sequence, TextIO, Iterable

Pair = Tuple[str, str]
PairSeq = Sequence[Pair]


@dataclass
class Documentation:
    summary: Optional[str] = None
    args: Optional[PairSeq] = None
    attrs: Optional[PairSeq] = None
    raises: Optional[PairSeq] = None
    returns: Optional[str] = None
    desc: Optional[str] = None
    todo: Optional[Sequence[str]] = None
    indent: int = 4
    wrap: int = 80

    def __post_init__(self):
        self._outer_wrapper = TextWrapper(width=self.wrap).wrap
        indent = ' ' * self.indent
        self._inner_wrapper = TextWrapper(
            width=self.wrap,
            initial_indent=indent,
            subsequent_indent=indent,
        ).wrap

    def inner_wrap(self, t: str) -> Iterable[str]:
        for line in self._inner_wrapper(t):
            yield line + '\n'

    def outer_wrap(self, t: str) -> Iterable[str]:
        for line in self._outer_wrapper(t):
            yield line + '\n'

    def __str__(self) -> str:
        with StringIO() as f:
            self.to_file(f)
            return f.getvalue()

    def print(self) -> None:
        self.to_file(stdout)

    def write_section(self, title: str, content: str, f: TextIO) -> None:
        if content:
            f.write(f'\n{title}:\n')
            f.writelines(self.inner_wrap(content))

    def write_pairs(self, title: str, pairs: PairSeq, f: TextIO) -> None:
        if pairs:
            f.write(f'\n{title}:\n')
            f.writelines(
                line
                for name, desc in pairs
                for line in self.inner_wrap(f'{name}: {desc}')
            )

    def to_file(self, f: TextIO) -> None:
        if self.summary:
            f.writelines(self.outer_wrap(self.summary))

        self.write_pairs('Arguments', self.args, f)
        self.write_pairs('Attributes', self.attrs, f)
        self.write_pairs('Raises', self.raises, f)
        self.write_section('Returns', self.returns, f)
        self.write_section('Description', self.desc, f)

        if self.todo:
            f.write(f'\nTodo:\n')
            f.writelines(
                line
                for todo in self.todo
                for line in self.inner_wrap(f'* {todo}')
            )


def test():
    Documentation(
        summary='The Pear object describes the properties of pears.',
        args=(
            ('a', 'Function argument'),
            ('b', 'Function argument'),
        ),
        attrs=(
            ('a', 'Information about parameter a'),
            ('b', 'Information about parameter b'),
        ),
        raises=(
            ('AttributeError', 'The ``Raises`` section is a list of all '
             'exceptions that are relevant to the interface.'),
        ),
        returns='The return value. True for success, False otherwise.',
        desc='The description may span multiple lines. Following lines should be indented. The type is optional. The description may '
        'span multiple lines. Following lines should be indented. The type is optional.The description may span multiple lines. '
        'Following lines should be indented. The type is optional.The description may span multiple lines. Following lines '
        'should be indented. The type is optional.',
        todo=('Do Something', 'Something else'),
    ).print()


if __name__ == '__main__':
    test()

Output

The Pear object describes the properties of pears.

Arguments:
    a: Function argument
    b: Function argument

Attributes:
    a: Information about parameter a
    b: Information about parameter b

Raises:
    AttributeError: The ``Raises`` section is a list of all exceptions that are
    relevant to the interface.

Returns:
    The return value. True for success, False otherwise.

Description:
    The description may span multiple lines. Following lines should be indented.
    The type is optional. The description may span multiple lines. Following
    lines should be indented. The type is optional.The description may span
    multiple lines. Following lines should be indented. The type is optional.The
    description may span multiple lines. Following lines should be indented. The
    type is optional.

Todo:
    * Do Something
    * Something else