PK!pytest_docs/__init__.pyPK!pytest_docs/element.pyimport inspect from enum import Enum from _pytest.mark.structures import MarkDecorator from pytest import Function from .utils import methdispatch PYTEST_DOC_MARKER = "pytest_doc" class ElementType(Enum): FUNCTION = "function" CLASS = "class" MODULE = "module" NONE = None class Element: def __init__(self, element: "Element" = None): self.raw_element = element self.raw_markers = self.element_markers(element) self.type_ = self._element_type(element) if element else ElementType.NONE self.unique_id = self.unique_identifier(element) self.raw_name = self.element_name(element) self.desc = self.element_desc(element) self.parent = None self.children = [] def __iter__(self): return iter(self.children) @property def top(self): return self.parent is None @property def siblings(self): return (elem for elem in self.parent if elem is not self) if self.parent else () @methdispatch def _element_type(self, element): return ( ElementType.CLASS if hasattr(element, "__qualname__") else ElementType.MODULE ) @_element_type.register(Function) def _(self, element): return ElementType.FUNCTION @classmethod def create_doc_tree(cls, items) -> "Element": tree = cls() for item in items: tree.add(cls(item.module)).add(cls(item.cls)).add(cls(item)) return tree @methdispatch def element_name(self, element): return element.__name__ if element else "" @element_name.register(Function) def _(self, element): return element.originalname or element.name @methdispatch def element_desc(self, element): return element.__doc__ if element else "" @element_desc.register(Function) def _(self, element): return element.function.__doc__ @methdispatch def format_marker(self, marker_data): data = ["(", ")"] data.insert(1, "".join(["{}".format(arg) for arg in marker_data])) return "".join(data) @format_marker.register(dict) def _(self, marker_data): data = ["(", ")"] data.insert( 1, "".join(["{}={}".format(key, value) for key, value in marker_data.items()]), ) return "".join(data) def marker_details(self, marker): args = kwargs = "" if marker.args: args = self.format_marker(marker.args) if marker.kwargs: kwargs = self.format_marker(marker.kwargs) out = [marker.name, args, kwargs] return " ".join(out).strip() @methdispatch def get_marker(self, marker): return self.marker_details(marker) @get_marker.register(MarkDecorator) def _(self, marker): return self.marker_details(marker.mark) def add(self, element: "Element") -> "Element": if element not in self.children: element.parent = self self.children.append(element) return self.children[self.children.index(element)] def format_markers(self, markers): return [self.get_marker(marker) for marker in markers] @methdispatch def element_markers(self, element): markers = getattr(element, "pytestmark", []) if not isinstance(markers, list): markers = [markers] return markers @element_markers.register(Function) def _(self, element): return element.own_markers @methdispatch def unique_identifier(self, element): if not element: return None qualname = getattr(element, "__qualname__", None) source_file = inspect.getsourcefile(element) if qualname: return "{}/{}".format(source_file, qualname) return source_file @unique_identifier.register(Function) def _(self, element): return element.nodeid @property def markers(self) -> list: return self.format_markers(self.raw_markers) @property def name(self) -> str: for marker in self.raw_markers: if marker.name == PYTEST_DOC_MARKER: return marker.kwargs["name"] return self.raw_name def __repr__(self): return "".format(self.name) def __str__(self): return self.name def __hash__(self): return hash(self.unique_id) def __eq__(self, other): if not isinstance(other, Element): raise TypeError return self.unique_id == other.unique_id PK!ln_{{pytest_docs/formatter.pyfrom itertools import chain from .element import Element class Formatter: name = None @staticmethod def _default_format(element) -> str: return element module_name_format = _default_format module_desc_format = _default_format class_name_format = _default_format class_desc_format = _default_format func_name_format = _default_format func_desc_format = _default_format marker_format = _default_format marker_prefix = "Markers" def create_document(self, doc_tree: Element) -> str: out = [] for module in doc_tree: out += self._doc_element( module, self.module_name_format, self.module_desc_format ) for class_ in module: out += self._doc_element( class_, self.class_name_format, self.class_desc_format ) for func in class_: out += self._doc_element( func, self.func_name_format, self.func_desc_format ) return "".join(out).lstrip("\n") def _element_markers(self, element: Element) -> list: marker_doc = [] if element.markers: marker_doc.append(self.marker_prefix) for marker in element.markers: marker_doc.append(self.marker_format(marker)) return marker_doc def _doc_element(self, element, element_name_fmt, element_desc_fmt) -> list: element_doc = [element_name_fmt(element.name)] if element.desc: element_doc.append(element_desc_fmt(element.desc)) element_doc += self._element_markers(element) element_doc = self._add_new_lines(element_doc) return element_doc @staticmethod def _add_new_lines(element_doc: list) -> list: return list(chain.from_iterable(zip(element_doc, ["\n" for _ in element_doc]))) PK!"pytest_docs/formatters/__init__.pyPK!"pytest_docs/formatters/markdown.pyfrom pytest_docs.formatter import Formatter class MarkdownFormatter(Formatter): name = "md" marker_prefix = "\n**Markers:**" @staticmethod def module_name_format(element): return "# {}".format(element) @staticmethod def class_name_format(element): return "## {}".format(element) @staticmethod def func_name_format(element): return "### {}".format(element) @staticmethod def marker_format(marker): return "- {}".format(marker) PK!b?;;%pytest_docs/formatters/restuctured.pyfrom pytest_docs.formatter import Formatter class RSTFormatter(Formatter): name = "rst" marker_prefix = "\n**Markers:**" @staticmethod def module_name_format(element): return "\n{}\n{}".format(element, "*" * len(element)) @staticmethod def class_name_format(element): return "\n{}\n{}".format(element, "-" * len(element)) @staticmethod def func_name_format(element): return "\n{}\n{}".format(element, "=" * len(element)) @staticmethod def marker_format(marker): return "\n- {}".format(marker) PK!G Smmpytest_docs/plugin.pyfrom pathlib import Path from .element import Element from .formatters.markdown import MarkdownFormatter from .formatters.restuctured import RSTFormatter FORMATTERS = { MarkdownFormatter.name: MarkdownFormatter(), RSTFormatter.name: RSTFormatter(), } class DocPlugin: def __init__(self, config): self.config = config self.path = config.getvalue("docs_path") self.format_type = config.getvalue("docs_type") def pytest_runtestloop(self, session): if not self.path: return doc_tree = Element.create_doc_tree(session.items) fmt = FORMATTERS[self.format_type] out = fmt.create_document(doc_tree) with Path(self.path).open("w") as file: file.write(out) def pytest_terminal_summary(self, terminalreporter, exitstatus): if self.path: terminalreporter.write_sep("-", "generated doc file: {}".format(self.path)) def pytest_addoption(parser): group = parser.getgroup("docs generator") group.addoption("--docs", dest="docs_path", help="create documentation given path") group.addoption( "--doc-type", dest="docs_type", default="md", help="Choose document type", choices=list(FORMATTERS), ) def pytest_configure(config): docs = DocPlugin(config) config.pluginmanager.register(docs, "pytest-docs") PK!49iipytest_docs/utils.pyfrom functools import singledispatch, update_wrapper # thanks to https://stackoverflow.com/a/24602374 def methdispatch(func): dispatcher = singledispatch(func) def wrapper(*args, **kw): return dispatcher.dispatch(args[1].__class__)(*args, **kw) wrapper.register = dispatcher.register update_wrapper(wrapper, func) return wrapper PK!? y44#pytest_docs-0.0.2.dist-info/LICENSE The MIT License (MIT) Copyright (c) 2018 Or Carmi Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!H\TT!pytest_docs-0.0.2.dist-info/WHEEL 1 0 нR \I$ơ7.ZON `h6oi14m,b4>4ɛpK>X;baP>PK!HB$pytest_docs-0.0.2.dist-info/METADATAQN0+ mDETiEY#g,Ş))cn;PC>r/91DQ.d!올?(&,1<u!?YY=9zo.d`yf+N^fZ0]F5=&˞#A,,eyv]ʂwi ̩+ _9q'`q-߂kS|4\,g;.f;.g;θ6!}PK!HT"pytest_docs-0.0.2.dist-info/RECORDϻ@|\r2"&"t~7*ڝh|EY?D%U{ O^ڪn_q`!-J>W}RG&osD]TgD('dWMed)^wpqaTh_ZYcWs֟ vn\P@{ nF )<{ndR+.<p<5(%?㘒etMX}+̙)kkHâPrY*q:w/ -uiwcd(7m$e3,0([eܨL{9g'̊Y][6/:ytּ~PK!pytest_docs/__init__.pyPK!5pytest_docs/element.pyPK!ln_{{mpytest_docs/formatter.pyPK!"pytest_docs/formatters/__init__.pyPK!"^pytest_docs/formatters/markdown.pyPK!b?;;%pytest_docs/formatters/restuctured.pyPK!G Smmpytest_docs/plugin.pyPK!49ii$pytest_docs/utils.pyPK!? y44#O&pytest_docs-0.0.2.dist-info/LICENSEPK!H\TT!*pytest_docs-0.0.2.dist-info/WHEELPK!HB$W+pytest_docs-0.0.2.dist-info/METADATAPK!HT",pytest_docs-0.0.2.dist-info/RECORDPK .