Skip to content

Commit

Permalink
refactor: use _init_subclass to register docstring parsers
Browse files Browse the repository at this point in the history
  • Loading branch information
jnoortheen committed Nov 26, 2020
1 parent b06b313 commit ac486bd
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 25 deletions.
37 changes: 18 additions & 19 deletions arger/docstring.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import functools
import inspect
import re
import typing as tp
Expand Down Expand Up @@ -41,7 +40,22 @@ class DocstringParser:
section_ptrn: tp.Pattern
param_ptrn: tp.Pattern

def parse(self, doc: str) -> DocstringTp:
_parsers: tp.List['DocstringParser'] = []

def __init_subclass__(cls, **_):
# Cache costly init phase per session.
cls._parsers.append(cls())

@classmethod
def parse(cls, func: tp.Optional[tp.Callable]) -> DocstringTp:
doc = (inspect.getdoc(func) or '') if func else ''
if doc:
for parser in cls._parsers:
if parser.matches(doc):
return parser._parse(doc)
return DocstringTp(description=doc, epilog='', params={})

def _parse(self, doc: str) -> DocstringTp:
raise NotImplementedError

def matches(self, doc: str) -> bool:
Expand Down Expand Up @@ -81,7 +95,7 @@ def parse_params(self, params: str) -> tp.Dict[str, ParamDocTp]:
for param, tphint, doc in docs
}

def parse(self, doc: str) -> DocstringTp:
def _parse(self, doc: str) -> DocstringTp:
desc, _, params = self.pattern.split(doc, maxsplit=1)
other_sect, params = self.get_rest_of_section(params)
return DocstringTp(desc.strip(), other_sect, params=self.parse_params(params))
Expand Down Expand Up @@ -121,7 +135,7 @@ def parse_doc(self, line: str, params: tp.Dict[str, ParamDocTp]):
type_hint = parts[0].strip()
params[param] = ParamDocTp.init(type_hint, doc)

def parse(self, doc: str) -> DocstringTp:
def _parse(self, doc: str) -> DocstringTp:
lines = self.pattern.split(doc)
long_desc = lines.pop(0)
epilog = ''
Expand All @@ -133,18 +147,3 @@ def parse(self, doc: str) -> DocstringTp:
self.parse_doc(sections[0], params)

return DocstringTp(long_desc.strip(), epilog, params)


@functools.lru_cache(None)
def _docstring_parsers():
"""Cache costly init phase per session."""
return [NumpyDocParser(), GoogleDocParser(), RstDocParser()]


def parse_docstring(func: tp.Optional[tp.Callable]) -> DocstringTp:
doc = (inspect.getdoc(func) or '') if func else ''
if doc:
for parser in _docstring_parsers():
if parser.matches(doc):
return parser.parse(doc)
return DocstringTp(description=doc, epilog='', params={})
8 changes: 4 additions & 4 deletions arger/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from typing import Any, Tuple, Union

from arger import typing_utils as tp_utils
from arger.docstring import DocstringTp, ParamDocTp, parse_docstring
from arger.docstring import DocstringParser, DocstringTp, ParamDocTp

CMD_TITLE = "commands"
LEVEL = '__level__'
Expand Down Expand Up @@ -170,7 +170,7 @@ def __init__(
self.sub_parser_action: tp.Optional[ap._SubParsersAction] = None

self.args: tp.Dict[str, Argument] = OrderedDict()
docstr = parse_docstring(func) if _doc_str is None else _doc_str
docstr = DocstringParser.parse(func) if _doc_str is None else _doc_str
kwargs.setdefault("description", docstr.description)
kwargs.setdefault("epilog", docstr.epilog)

Expand Down Expand Up @@ -201,7 +201,7 @@ def _add_arguments(self, func: tp.Callable, docstr: DocstringTp, level: int):
self._add_arg(arg)

def _add_arg(self, arg: Argument):
self.add_argument(*arg.flags, **arg.kwargs)
return self.add_argument(*arg.flags, **arg.kwargs)

def run(self, *args: str, capture_sys=True) -> ap.Namespace:
"""Parse cli and dispatch functions.
Expand Down Expand Up @@ -244,7 +244,7 @@ def add_cmd(self, func: tp.Callable) -> ap.ArgumentParser:
if not self.sub_parser_action:
self.sub_parser_action = self.add_subparsers(title=CMD_TITLE)

docstr = parse_docstring(func)
docstr = DocstringParser.parse(func)
return self.sub_parser_action.add_parser(
name=func.__name__,
help=docstr.description,
Expand Down
4 changes: 2 additions & 2 deletions tests/test_docstrings_parser.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pytest

from arger.docstring import ParamDocTp, parse_docstring
from arger.docstring import DocstringParser, ParamDocTp


def func_numpy():
Expand Down Expand Up @@ -69,7 +69,7 @@ def func_rst():
],
)
def test_docstring_parser(fn):
result = parse_docstring(fn)
result = DocstringParser.parse(fn)
assert result.description == "Summary line.\n\nExtended description of function."
assert list(result.params) == ['arg1', 'arg2', 'arg3', 'arg4']
assert list(result.params.values()) == [
Expand Down

0 comments on commit ac486bd

Please sign in to comment.