Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-docstring-to-markdown for
openSUSE:Factory checked in at 2026-04-01 19:52:13
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-docstring-to-markdown (Old)
and /work/SRC/openSUSE:Factory/.python-docstring-to-markdown.new.21863
(New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-docstring-to-markdown"
Wed Apr 1 19:52:13 2026 rev:7 rq:1344057 version:0.17
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-docstring-to-markdown/python-docstring-to-markdown.changes
2024-04-07 22:13:58.308107118 +0200
+++
/work/SRC/openSUSE:Factory/.python-docstring-to-markdown.new.21863/python-docstring-to-markdown.changes
2026-04-01 19:53:43.388842706 +0200
@@ -1,0 +2,12 @@
+Wed Apr 1 07:47:58 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 0.17:
+ * Fix test failures with `importlib-metadata` 8.7.0
+ * Fix reStructuredText edge cases to better support `polars`
+ * Use trusted publishing for package uploads
+- update to 0.16:
+ * Implement entry points to allow extension/customization
+ * Drop Python 3.7 from testing, add 3.12 and 3.13, update
+ actions
+
+-------------------------------------------------------------------
Old:
----
docstring-to-markdown-0.15-gh.tar.gz
New:
----
docstring-to-markdown-0.17-gh.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-docstring-to-markdown.spec ++++++
--- /var/tmp/diff_new_pack.3SxiNt/_old 2026-04-01 19:53:45.236919468 +0200
+++ /var/tmp/diff_new_pack.3SxiNt/_new 2026-04-01 19:53:45.248919967 +0200
@@ -1,7 +1,7 @@
#
# spec file for package python-docstring-to-markdown
#
-# Copyright (c) 2024 SUSE LLC
+# Copyright (c) 2026 SUSE LLC and contributors
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -17,28 +17,31 @@
%{?sle15_python_module_pythons}
-
Name: python-docstring-to-markdown
-Version: 0.15
+Version: 0.17
Release: 0
Summary: On the fly conversion of Python docstrings to markdown
-License: LGPL-2.1-only
+License: LGPL-2.1-or-later
URL: https://github.com/python-lsp/docstring-to-markdown
Source:
https://github.com/python-lsp/docstring-to-markdown/archive/refs/tags/v%{version}.tar.gz#/docstring-to-markdown-%{version}-gh.tar.gz
-BuildRequires: %{python_module base >= 3.6}
+BuildRequires: %{python_module base >= 3.7}
+BuildRequires: %{python_module importlib_metadata >= 3.6}
BuildRequires: %{python_module pip}
BuildRequires: %{python_module pytest}
BuildRequires: %{python_module setuptools}
+BuildRequires: %{python_module typing_extensions >= 4.6}
BuildRequires: %{python_module wheel}
BuildRequires: fdupes
BuildRequires: python-rpm-macros
+Requires: python-importlib_metadata >= 3.6
+Requires: python-typing_extensions >= 4.6
Provides: python-docstring_to_markdown = %{version}-%{release}
BuildArch: noarch
%python_subpackages
%description
On the fly conversion of Python docstrings to markdown
- - Python 3.6+
+ - Python 3.7+
- currently can recognise reStructuredText and convert
multiple of its features to Markdown
- in the future will be able to convert Google docstrings too
++++++ docstring-to-markdown-0.15-gh.tar.gz ->
docstring-to-markdown-0.17-gh.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/docstring-to-markdown-0.15/.github/workflows/publish.yml
new/docstring-to-markdown-0.17/.github/workflows/publish.yml
--- old/docstring-to-markdown-0.15/.github/workflows/publish.yml
2024-02-21 14:50:11.000000000 +0100
+++ new/docstring-to-markdown-0.17/.github/workflows/publish.yml
2025-05-02 17:07:43.000000000 +0200
@@ -5,23 +5,39 @@
types: [created]
jobs:
- deploy:
+ build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - name: Set up Python 3.8
- uses: actions/setup-python@v2
+ - uses: actions/checkout@v4
+ - name: Set up Python 3.9
+ uses: actions/setup-python@v5
with:
- python-version: 3.8
+ python-version: 3.9
- name: Install build dependencies
run: |
- python -m pip install --upgrade pip wheel
+ python -m pip install --upgrade pip wheel build
- name: Build package
run: |
- python setup.py sdist bdist_wheel
- - name: Publish a Python distribution to PyPI
- uses: pypa/[email protected]
+ python -m build
+ - name: Upload Artifact
+ uses: actions/upload-artifact@v4
with:
- user: __token__
- password: ${{ secrets.PYPI_UPLOAD_API_TOKEN }}
-
+ name: docstring-to-markdown dist ${{ github.run_number }}
+ path: ./dist
+ pypi-publish:
+ name: Upload release to PyPI
+ runs-on: ubuntu-latest
+ needs: [build]
+ environment:
+ name: pypi
+ url: https://pypi.org/p/docstring-to-markdown
+ permissions:
+ id-token: write
+ steps:
+ - name: Download artifacts
+ uses: actions/download-artifact@v4
+ with:
+ name: docstring-to-markdown dist ${{ github.run_number }}
+ path: ./dist
+ - name: Publish package distributions to PyPI
+ uses: pypa/gh-action-pypi-publish@release/v1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/docstring-to-markdown-0.15/.github/workflows/tests.yml
new/docstring-to-markdown-0.17/.github/workflows/tests.yml
--- old/docstring-to-markdown-0.15/.github/workflows/tests.yml 2024-02-21
14:50:11.000000000 +0100
+++ new/docstring-to-markdown-0.17/.github/workflows/tests.yml 2025-05-02
17:07:43.000000000 +0200
@@ -11,17 +11,17 @@
strategy:
matrix:
os: [ubuntu-latest]
- python-version: [3.7, 3.8, 3.9, '3.10', '3.11']
+ python-version: [3.8, 3.9, '3.10', '3.11', '3.12', '3.13']
runs-on: ${{ matrix.os }}
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install test dependencies
run: |
- python -m pip install --upgrade pip wheel
+ python -m pip install --upgrade pip wheel build
python -m pip install -r requirements-dev.txt
- name: Temporary installation
run: python -m pip install -e .
@@ -33,8 +33,8 @@
mypy docstring_to_markdown
- name: Build package
run: |
- python setup.py sdist bdist_wheel
+ python -m build
- name: Install package
- run: python -m pip install --find-links=dist --no-index
--ignore-installed docstring_to_markdown
+ run: python -m pip install --find-links=dist --ignore-installed
docstring_to_markdown
- name: Pip check
run: python -m pip check
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/docstring-to-markdown-0.15/README.md
new/docstring-to-markdown-0.17/README.md
--- old/docstring-to-markdown-0.15/README.md 2024-02-21 14:50:11.000000000
+0100
+++ new/docstring-to-markdown-0.17/README.md 2025-05-02 17:07:43.000000000
+0200
@@ -6,7 +6,7 @@
On the fly conversion of Python docstrings to markdown
-- Python 3.6+ (tested on 3.7 up to 3.11)
+- Python 3.7+ (tested on 3.8 up to 3.13)
- can recognise reStructuredText and convert multiple of its features to
Markdown
- since v0.13 includes initial support for Google-formatted docstrings
@@ -35,6 +35,11 @@
docstring_to_markdown.UnknownFormatError
```
+### Extensibility
+
+`docstring_to_markdown` entry point group allows to add custom converters
which follow the `Converter` protocol.
+The built-in converters can be customized by providing entry point with
matching name.
+
### Development
```bash
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/docstring-to-markdown-0.15/docstring_to_markdown/__init__.py
new/docstring-to-markdown-0.17/docstring_to_markdown/__init__.py
--- old/docstring-to-markdown-0.15/docstring_to_markdown/__init__.py
2024-02-21 14:50:11.000000000 +0100
+++ new/docstring-to-markdown-0.17/docstring_to_markdown/__init__.py
2025-05-02 17:07:43.000000000 +0200
@@ -1,27 +1,59 @@
-from .cpython import cpython_to_markdown
-from .google import google_to_markdown, looks_like_google
-from .plain import looks_like_plain_text, plain_text_to_markdown
-from .rst import looks_like_rst, rst_to_markdown
+from importlib_metadata import entry_points
+from typing import List, TYPE_CHECKING
-__version__ = "0.15"
+from .types import Converter
+
+if TYPE_CHECKING:
+ from importlib_metadata import EntryPoint
+
+__version__ = "0.17"
class UnknownFormatError(Exception):
pass
-def convert(docstring: str) -> str:
- if looks_like_rst(docstring):
- return rst_to_markdown(docstring)
+def _entry_points_sort_key(entry_point: 'EntryPoint'):
+ if entry_point.dist is None:
+ return 1
+ if entry_point.dist.name == "docstring-to-markdown":
+ return 0
+ return 1
+
+
+def _load_converters() -> List[Converter]:
+ converter_entry_points = entry_points(
+ group="docstring_to_markdown"
+ )
+ # sort so that the default ones can be overridden
+ sorted_entry_points = sorted(
+ converter_entry_points,
+ key=_entry_points_sort_key
+ )
+ # de-duplicate
+ unique_entry_points = {}
+ for entry_point in sorted_entry_points:
+ unique_entry_points[entry_point.name] = entry_point
+
+ converters = []
+ for entry_point in unique_entry_points.values():
+ converter_class = entry_point.load()
+ converters.append(converter_class())
+
+ converters.sort(key=lambda converter: -converter.priority)
- if looks_like_google(docstring):
- return google_to_markdown(docstring)
+ return converters
- if looks_like_plain_text(docstring):
- return plain_text_to_markdown(docstring)
- cpython = cpython_to_markdown(docstring)
- if cpython:
- return cpython
+_CONVERTERS = None
+
+
+def convert(docstring: str) -> str:
+ global _CONVERTERS
+ if _CONVERTERS is None:
+ _CONVERTERS = _load_converters()
+ for converter in _CONVERTERS:
+ if converter.can_convert(docstring):
+ return converter.convert(docstring)
raise UnknownFormatError()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/docstring-to-markdown-0.15/docstring_to_markdown/cpython.py
new/docstring-to-markdown-0.17/docstring_to_markdown/cpython.py
--- old/docstring-to-markdown-0.15/docstring_to_markdown/cpython.py
2024-02-21 14:50:11.000000000 +0100
+++ new/docstring-to-markdown-0.17/docstring_to_markdown/cpython.py
2025-05-02 17:07:43.000000000 +0200
@@ -1,8 +1,10 @@
from typing import Union, List
from re import fullmatch
+from .types import Converter
from ._utils import escape_markdown
+
def _is_cpython_signature_line(line: str) -> bool:
"""CPython uses signature lines in the following format:
@@ -30,8 +32,29 @@
escape_markdown('\n'.join(other_lines))
])
+
def looks_like_cpython(text: str) -> bool:
return cpython_to_markdown(text) is not None
-__all__ = ['looks_like_cpython', 'cpython_to_markdown']
+class CPythonConverter(Converter):
+
+ priority = 10
+
+ def __init__(self) -> None:
+ self._last_docstring: Union[str, None] = None
+ self._converted: Union[str, None] = None
+
+ def can_convert(self, docstring):
+ self._last_docstring = docstring
+ self._converted = cpython_to_markdown(docstring)
+ return self._converted is not None
+
+ def convert(self, docstring):
+ if docstring != self._last_docstring:
+ self._last_docstring = docstring
+ self._converted = cpython_to_markdown(docstring)
+ return self._converted
+
+
+__all__ = ['looks_like_cpython', 'cpython_to_markdown', 'CPythonConverter']
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/docstring-to-markdown-0.15/docstring_to_markdown/google.py
new/docstring-to-markdown-0.17/docstring_to_markdown/google.py
--- old/docstring-to-markdown-0.15/docstring_to_markdown/google.py
2024-02-21 14:50:11.000000000 +0100
+++ new/docstring-to-markdown-0.17/docstring_to_markdown/google.py
2025-05-02 17:07:43.000000000 +0200
@@ -2,6 +2,9 @@
from textwrap import dedent
from typing import List
+from .types import Converter
+
+
# All possible sections in Google style docstrings
SECTION_HEADERS: List[str] = [
"Args",
@@ -169,3 +172,14 @@
docstring = GoogleDocstring(text)
return docstring.as_markdown()
+
+
+class GoogleConverter(Converter):
+
+ priority = 75
+
+ def can_convert(self, docstring):
+ return looks_like_google(docstring)
+
+ def convert(self, docstring):
+ return google_to_markdown(docstring)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/docstring-to-markdown-0.15/docstring_to_markdown/plain.py
new/docstring-to-markdown-0.17/docstring_to_markdown/plain.py
--- old/docstring-to-markdown-0.15/docstring_to_markdown/plain.py
2024-02-21 14:50:11.000000000 +0100
+++ new/docstring-to-markdown-0.17/docstring_to_markdown/plain.py
2025-05-02 17:07:43.000000000 +0200
@@ -1,4 +1,5 @@
from re import fullmatch
+from .types import Converter
from ._utils import escape_markdown
@@ -24,4 +25,16 @@
def plain_text_to_markdown(text: str) -> str:
return escape_markdown(text)
-__all__ = ['looks_like_plain_text', 'plain_text_to_markdown']
+
+class PlainTextConverter(Converter):
+
+ priority = 50
+
+ def can_convert(self, docstring):
+ return looks_like_plain_text(docstring)
+
+ def convert(self, docstring):
+ return plain_text_to_markdown(docstring)
+
+
+__all__ = ['looks_like_plain_text', 'plain_text_to_markdown',
'PlainTextConverter']
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/docstring-to-markdown-0.15/docstring_to_markdown/rst.py
new/docstring-to-markdown-0.17/docstring_to_markdown/rst.py
--- old/docstring-to-markdown-0.15/docstring_to_markdown/rst.py 2024-02-21
14:50:11.000000000 +0100
+++ new/docstring-to-markdown-0.17/docstring_to_markdown/rst.py 2025-05-02
17:07:43.000000000 +0200
@@ -1,9 +1,12 @@
from abc import ABC, abstractmethod
from enum import IntEnum, auto
+from textwrap import dedent
from types import SimpleNamespace
from typing import Callable, Match, Union, List, Dict
import re
+from .types import Converter
+
class Directive:
def __init__(
@@ -297,8 +300,8 @@
SECTION_DIRECTIVES: Dict[str, List[Directive]] = {
'Parameters': [
Directive(
- pattern=r'^(?P<other_args>\*\*kwargs|\*args)$',
- replacement=r'- `\g<other_args>`'
+ pattern=r'^(?P<other_args>(\w[\w\d_\.]*)|\*\*kwargs|\*args)$',
+ replacement=r'- `\g<other_args>`:'
),
Directive(
pattern=r'^(?P<arg1>[^:\s]+\d), (?P<arg2>[^:\s]+\d), \.\.\. :
(?P<type>.+)$',
@@ -334,6 +337,7 @@
def looks_like_rst(value: str) -> bool:
+ value = dedent(value)
# check if any of the characteristic sections (and the properly formatted
underline) is there
for section in _RST_SECTIONS:
if (section + '\n' + '-' * len(section) + '\n') in value:
@@ -532,7 +536,7 @@
self._rows.append(self._split(line))
self._expecting_row_content = not self._expecting_row_content
else:
- self._state += 1
+ self._state += 1 # pragma: no cover
class BlockParser(IParser):
@@ -540,10 +544,20 @@
follower: Union['IParser', None] = None
_buffer: List[str]
_block_started: bool
+ _indent: Union[int, None]
+ should_measure_indent = True
def __init__(self):
self._buffer = []
self._block_started = False
+ self._indent = None
+
+ def measure_indent(self, line: str):
+ line_indent = len(line) - len(line.lstrip())
+ if self._indent is None:
+ self._indent = line_indent
+ else:
+ self._indent = min(line_indent, self._indent)
@abstractmethod
def can_parse(self, line: str) -> bool:
@@ -556,6 +570,8 @@
def consume(self, line: str):
if not self._block_started:
raise ValueError('Block has not started') # pragma: no cover
+ if self.should_measure_indent:
+ self.measure_indent(line)
self._buffer.append(line)
def finish_consumption(self, final: bool) -> str:
@@ -563,17 +579,24 @@
if self._buffer[len(self._buffer) - 1].strip() == '':
self._buffer.pop()
self._buffer.append(self.enclosure + '\n')
- result = '\n'.join(self._buffer)
+ indent = " " * (self._indent or 0)
+ intermediate = '\n'.join(self._buffer)
+ result = '\n'.join([
+ (indent + line) if line else line
+ for line in intermediate.splitlines()
+ ]) if indent else intermediate
if not final:
result += '\n'
self._buffer = []
self._block_started = False
+ self._indent = None
return result
class IndentedBlockParser(BlockParser, ABC):
_is_block_beginning: bool
_block_indent_size: Union[int, None]
+ should_measure_indent = False
def __init__(self):
super(IndentedBlockParser, self).__init__()
@@ -597,6 +620,7 @@
return
if self._block_indent_size is None:
self._block_indent_size = len(line) - len(line.lstrip())
+ self.measure_indent(line)
super().consume(line[self._block_indent_size:])
def finish_consumption(self, final: bool) -> str:
@@ -651,11 +675,13 @@
if line.strip() == '.. autosummary::':
language = ''
line = ''
+ suffix = ''
else:
line = re.sub(r'::$', '', line)
+ suffix = '\n\n'
self._start_block(language)
- return IBlockBeginning(remainder=line.rstrip() + '\n\n')
+ return IBlockBeginning(remainder=line.rstrip() + suffix)
class MathBlockParser(IndentedBlockParser):
@@ -680,6 +706,7 @@
return line.strip() in self.directives
def initiate_parsing(self, line: str, current_language: str):
+ self.measure_indent(line)
admonition = self.directives[line.strip()]
self._start_block(f'\n{admonition.block_markdown}\n')
return IBlockBeginning(remainder='')
@@ -690,6 +717,7 @@
return re.match(CODE_BLOCK_PATTERN, line) is not None
def initiate_parsing(self, line: str, current_language: str) ->
IBlockBeginning:
+ self.measure_indent(line)
match = re.match(CODE_BLOCK_PATTERN, line)
# already checked in can_parse
assert match
@@ -749,6 +777,8 @@
most_recent_section: Union[str, None] = None
is_first_line = True
+ text = dedent(text)
+
def flush_buffer():
nonlocal lines_buffer
lines = '\n'.join(lines_buffer)
@@ -762,7 +792,8 @@
lines_buffer = []
return lines
- for line in text.split('\n'):
+ lines = text.split('\n')
+ for i, line in enumerate(lines):
if is_first_line:
if extract_signature:
signature_match =
re.match(r'^(?P<name>\S+)\((?P<params>.*)\)$', line)
@@ -805,7 +836,9 @@
else:
if most_recent_section in SECTION_DIRECTIVES:
for section_directive in
SECTION_DIRECTIVES[most_recent_section]:
- if re.match(section_directive.pattern, trimmed_line):
+ next_line = lines[i + 1] if i + 1 < len(lines) else ""
+ is_next_line_section = set(next_line.strip()) == {"-"}
+ if re.match(section_directive.pattern, line) and not
is_next_line_section:
line = re.sub(section_directive.pattern,
section_directive.replacement, trimmed_line)
break
if trimmed_line.rstrip() in RST_SECTIONS:
@@ -825,3 +858,14 @@
if active_parser:
markdown += active_parser.finish_consumption(True)
return markdown
+
+
+class ReStructuredTextConverter(Converter):
+
+ priority = 100
+
+ def can_convert(self, docstring):
+ return looks_like_rst(docstring)
+
+ def convert(self, docstring):
+ return rst_to_markdown(docstring)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/docstring-to-markdown-0.15/docstring_to_markdown/types.py
new/docstring-to-markdown-0.17/docstring_to_markdown/types.py
--- old/docstring-to-markdown-0.15/docstring_to_markdown/types.py
1970-01-01 01:00:00.000000000 +0100
+++ new/docstring-to-markdown-0.17/docstring_to_markdown/types.py
2025-05-02 17:07:43.000000000 +0200
@@ -0,0 +1,14 @@
+from typing_extensions import Protocol
+
+
+class Converter(Protocol):
+
+ def convert(self, docstring: str) -> str:
+ """Convert given docstring to markdown."""
+
+ def can_convert(self, docstring: str) -> bool:
+ """Check if conversion to markdown can be performed."""
+
+ # The higher the priority, the sooner the conversion
+ # with this converter will be attempted.
+ priority: int
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/docstring-to-markdown-0.15/pyproject.toml
new/docstring-to-markdown-0.17/pyproject.toml
--- old/docstring-to-markdown-0.15/pyproject.toml 1970-01-01
01:00:00.000000000 +0100
+++ new/docstring-to-markdown-0.17/pyproject.toml 2025-05-02
17:07:43.000000000 +0200
@@ -0,0 +1,3 @@
+[build-system]
+requires = ["setuptools"]
+build-backend = "setuptools.build_meta"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/docstring-to-markdown-0.15/requirements-dev.txt
new/docstring-to-markdown-0.17/requirements-dev.txt
--- old/docstring-to-markdown-0.15/requirements-dev.txt 2024-02-21
14:50:11.000000000 +0100
+++ new/docstring-to-markdown-0.17/requirements-dev.txt 2025-05-02
17:07:43.000000000 +0200
@@ -1,5 +1,5 @@
pytest
pytest-cov
pytest-flake8
-flake8<5
+flake8
mypy
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/docstring-to-markdown-0.15/setup.cfg
new/docstring-to-markdown-0.17/setup.cfg
--- old/docstring-to-markdown-0.15/setup.cfg 2024-02-21 14:50:11.000000000
+0100
+++ new/docstring-to-markdown-0.17/setup.cfg 2025-05-02 17:07:43.000000000
+0200
@@ -28,21 +28,36 @@
[options]
packages = find:
-python_requires = >=3.6
+python_requires = >=3.7
+install_requires =
+ importlib-metadata>=3.6
+ typing_extensions>=4.6
[options.package_data]
docstring-to-markdown = py.typed
+[options.entry_points]
+docstring_to_markdown =
+ rst = docstring_to_markdown.rst:ReStructuredTextConverter
+ google = docstring_to_markdown.google:GoogleConverter
+ plain = docstring_to_markdown.plain:PlainTextConverter
+ cpython = docstring_to_markdown.cpython:CPythonConverter
+
[tool:pytest]
addopts =
--pyargs tests
--cov docstring_to_markdown
- --cov-fail-under=99
+ --cov-fail-under=100
--cov-report term-missing:skip-covered
-p no:warnings
--flake8
-vv
+[coverage:report]
+exclude_lines =
+ pragma: no cover
+ if TYPE_CHECKING:
+
[flake8]
max-line-length = 120
max-complexity = 15
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/docstring-to-markdown-0.15/tests/test_convert.py
new/docstring-to-markdown-0.17/tests/test_convert.py
--- old/docstring-to-markdown-0.15/tests/test_convert.py 2024-02-21
14:50:11.000000000 +0100
+++ new/docstring-to-markdown-0.17/tests/test_convert.py 2025-05-02
17:07:43.000000000 +0200
@@ -1,4 +1,10 @@
+from contextlib import contextmanager
from docstring_to_markdown import convert, UnknownFormatError
+from docstring_to_markdown.types import Converter
+from docstring_to_markdown.cpython import CPythonConverter
+from importlib_metadata import EntryPoint, entry_points, distribution
+from unittest.mock import patch
+import docstring_to_markdown
import pytest
CPYTHON = """\
@@ -55,3 +61,72 @@
def test_unknown_format():
with pytest.raises(UnknownFormatError):
convert('ARGS [arg1, arg2] RETURNS: str OR None')
+
+
+class HighPriorityConverter(Converter):
+ priority = 120
+
+ def convert(self, docstring):
+ return "HighPriority"
+
+ def can_convert(self, docstring):
+ return True
+
+
+class MockEntryPoint(EntryPoint):
+ def load(self):
+ return globals()[self.attr]
+
+ dist = None
+
+
+class DistMockEntryPoint(MockEntryPoint):
+ # Pretend it is contributed by `pytest`.
+ # It could be anything else, but `pytest`
+ # is guaranteed to be installed during tests.
+ dist = distribution('pytest')
+
+
+class CustomCPythonConverter(CPythonConverter):
+ priority = 10
+
+ def convert(self, docstring):
+ return 'CustomCPython'
+
+ def can_convert(self, docstring):
+ return True
+
+
+@contextmanager
+def custom_entry_points(entry_points):
+ old = docstring_to_markdown._CONVERTERS
+ docstring_to_markdown._CONVERTERS = None
+ with patch.object(docstring_to_markdown, 'entry_points',
return_value=entry_points):
+ yield
+ docstring_to_markdown._CONVERTERS = old
+
+
+def test_adding_entry_point():
+ original_entry_points = entry_points(group="docstring_to_markdown")
+ mock_entry_point = MockEntryPoint(
+ name='high-priority-converter',
+ group='docstring_to_markdown',
+ value="here:HighPriorityConverter",
+ )
+ with custom_entry_points([*original_entry_points, mock_entry_point]):
+ assert convert('test') == 'HighPriority'
+
+
+def test_replacing_entry_point():
+ assert convert(CPYTHON) == CPYTHON_MD
+ original_entry_points = entry_points(group="docstring_to_markdown")
+ mock_entry_point = DistMockEntryPoint(
+ name='cpython',
+ group='docstring_to_markdown',
+ value="here:CustomCPythonConverter",
+ )
+ with custom_entry_points([*original_entry_points, mock_entry_point]):
+ assert convert('test') == 'test'
+ assert convert(GOOGLE) == GOOGLE_MD
+ assert convert(RST) == RST_MD
+ assert convert(CPYTHON) == 'CustomCPython'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/docstring-to-markdown-0.15/tests/test_cpython.py
new/docstring-to-markdown-0.17/tests/test_cpython.py
--- old/docstring-to-markdown-0.15/tests/test_cpython.py 2024-02-21
14:50:11.000000000 +0100
+++ new/docstring-to-markdown-0.17/tests/test_cpython.py 2025-05-02
17:07:43.000000000 +0200
@@ -1,5 +1,5 @@
import pytest
-from docstring_to_markdown.cpython import looks_like_cpython,
cpython_to_markdown
+from docstring_to_markdown.cpython import looks_like_cpython,
cpython_to_markdown, CPythonConverter
BOOL = """\
bool(x) -> bool
@@ -101,3 +101,10 @@
def test_conversion_str():
assert cpython_to_markdown(STR) == STR_MD
+
+
+def test_convert():
+ converter = CPythonConverter()
+ assert converter.can_convert(BOOL)
+ assert not converter.can_convert('this is plain text')
+ assert converter.convert(BOOL) == BOOL_MD
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/docstring-to-markdown-0.15/tests/test_google.py
new/docstring-to-markdown-0.17/tests/test_google.py
--- old/docstring-to-markdown-0.15/tests/test_google.py 2024-02-21
14:50:11.000000000 +0100
+++ new/docstring-to-markdown-0.17/tests/test_google.py 2025-05-02
17:07:43.000000000 +0200
@@ -1,6 +1,6 @@
import pytest
-from docstring_to_markdown.google import google_to_markdown, looks_like_google
+from docstring_to_markdown.google import google_to_markdown,
looks_like_google, GoogleConverter
BASIC_EXAMPLE = """Do **something**.
@@ -133,3 +133,10 @@
)
def test_google_to_markdown(google, markdown):
assert google_to_markdown(google) == markdown
+
+
+def test_converter():
+ converter = GoogleConverter()
+ assert converter.can_convert(BASIC_EXAMPLE)
+ assert not converter.can_convert("This is plain text")
+ assert converter.convert(BASIC_EXAMPLE) == BASIC_EXAMPLE_MD
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/docstring-to-markdown-0.15/tests/test_plain.py
new/docstring-to-markdown-0.17/tests/test_plain.py
--- old/docstring-to-markdown-0.15/tests/test_plain.py 2024-02-21
14:50:11.000000000 +0100
+++ new/docstring-to-markdown-0.17/tests/test_plain.py 2025-05-02
17:07:43.000000000 +0200
@@ -1,5 +1,5 @@
import pytest
-from docstring_to_markdown.plain import looks_like_plain_text,
plain_text_to_markdown
+from docstring_to_markdown.plain import looks_like_plain_text,
plain_text_to_markdown, PlainTextConverter
@pytest.mark.parametrize("text", [
@@ -40,3 +40,10 @@
def test_conversion():
assert plain_text_to_markdown("test") == "test"
+
+
+def test_convert():
+ converter = PlainTextConverter()
+ assert not converter.can_convert('def test():')
+ assert converter.can_convert('this is plain text')
+ assert converter.convert('test') == 'test'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/docstring-to-markdown-0.15/tests/test_rst.py
new/docstring-to-markdown-0.17/tests/test_rst.py
--- old/docstring-to-markdown-0.15/tests/test_rst.py 2024-02-21
14:50:11.000000000 +0100
+++ new/docstring-to-markdown-0.17/tests/test_rst.py 2025-05-02
17:07:43.000000000 +0200
@@ -1,6 +1,6 @@
import pytest
-from docstring_to_markdown.rst import looks_like_rst, rst_to_markdown
+from docstring_to_markdown.rst import looks_like_rst, rst_to_markdown,
ReStructuredTextConverter
SEE_ALSO = """
@@ -119,6 +119,26 @@
A function definition is an executable statement.
"""
+RST_AUTOSUMMARY_BLOCK = """
+Summary
+
+.. autosummary::
+
+ environment.BuildEnvironment
+ util.relative_uri
+"""
+
+
+RST_AUTOSUMMARY_BLOCK_MARKDOWN = """
+Summary
+
+```
+environment.BuildEnvironment
+util.relative_uri
+```
+"""
+
+
RST_COLON_CODE_BLOCK = """
For example, the following code ::
@@ -317,7 +337,7 @@
- `x`: array_like
Input array.
-- `**kwargs`
+- `**kwargs`:
For other keyword-only arguments, see the ufunc docs.
"""
@@ -598,6 +618,139 @@
"""
+BROKEN_GRID_TABLE = """
++------------+-----------+------------+-----------------+---+---------+
+|param_kernel|param_gamma|param_degree|split0_test_score|...|rank_t...|
++============+===========+============+=================+===+=========+
+| 'poly' | -- | 2 | 0.80 |...| 2 |
++------------+-----------+------------+-----------------+---+---------+
+| 'poly' | -- | 3 | 0.70 |...| 4 |
+someone forgot to close the row above.
+"""
+
+
+BROKEN_GRID_TABLE_MARKDOWN = """
+| param_kernel | param_gamma | param_degree | split0_test_score | ... |
rank_t... |
+| ------------ | ----------- | ------------ | ----------------- | --- |
--------- |
+| 'poly' | -- | 2 | 0.80 | ... | 2
|
+| 'poly' | -- | 3 | 0.70 | ... | 4
|
+someone forgot to close the row above.
+"""
+
+
+# this format is often used by polars
+PARAMETERS_WITHOUT_TYPE = """
+Parameters
+----------
+source
+ Path(s) to a file or directory
+ When needing to authenticate for scanning cloud locations, see the
+ `storage_options` parameter.
+columns
+ Columns to select. Accepts a list of column indices (starting at zero) or
a list
+ of column names.
+n_rows
+ Stop reading from parquet file after reading `n_rows`.
+ Only valid when `use_pyarrow=False`.
+
+Returns
+-------
+DataFrame
+"""
+
+PARAMETERS_WITHOUT_TYPE_MARKDOWN = """
+#### Parameters
+
+- `source`:
+ Path(s) to a file or directory
+ When needing to authenticate for scanning cloud locations, see the
+ `storage_options` parameter.
+- `columns`:
+ Columns to select. Accepts a list of column indices (starting at zero) or
a list
+ of column names.
+- `n_rows`:
+ Stop reading from parquet file after reading `n_rows`.
+ Only valid when `use_pyarrow=False`.
+
+#### Returns
+
+DataFrame
+"""
+
+INDENTED_DOCSTRING = """
+ Parameters
+ ----------
+ glob
+ Expand path given via globbing rules.
+"""
+
+INDENTED_DOCSTRING_MARKDOWN = """
+#### Parameters
+
+- `glob`:
+ Expand path given via globbing rules.
+"""
+
+
+WARNINGS_IN_PARAMETERS = """
+Parameters
+----------
+glob
+ Expand path given via globbing rules.
+schema
+ Specify the datatypes of the columns. The datatypes must match the
+ datatypes in the file(s). If there are extra columns that are not in the
+ file(s), consider also enabling `allow_missing_columns`.
+
+ .. warning::
+ This functionality is considered **unstable**. It may be changed
+ at any point without it being considered a breaking change.
+hive_schema
+ The column names and data types of the columns by which the data is
partitioned.
+ If set to `None` (default), the schema of the Hive partitions is inferred.
+
+ .. warning::
+ This functionality is considered **unstable**. It may be changed
+ at any point without it being considered a breaking change.
+try_parse_hive_dates
+ Whether to try parsing hive values as date/datetime types.
+"""
+
+
+WARNINGS_IN_PARAMETERS_MARKDOWN = """
+#### Parameters
+
+- `glob`:
+ Expand path given via globbing rules.
+- `schema`:
+ Specify the datatypes of the columns. The datatypes must match the
+ datatypes in the file(s). If there are extra columns that are not in the
+ file(s), consider also enabling `allow_missing_columns`.
+
+
+ ---
+ ⚠️ **Warning**
+
+ This functionality is considered **unstable**. It may be changed
+ at any point without it being considered a breaking change.
+
+ ---
+- `hive_schema`:
+ The column names and data types of the columns by which the data is
partitioned.
+ If set to `None` (default), the schema of the Hive partitions is inferred.
+
+
+ ---
+ ⚠️ **Warning**
+
+ This functionality is considered **unstable**. It may be changed
+ at any point without it being considered a breaking change.
+
+ ---
+- `try_parse_hive_dates`:
+ Whether to try parsing hive values as date/datetime types.
+"""
+
NESTED_PARAMETERS = """
Parameters
----------
@@ -733,6 +886,10 @@
'rst': NUMPY_EXAMPLE,
'md': NUMPY_EXAMPLE_MARKDOWN
},
+ 'converts autosummary block': {
+ 'rst': RST_AUTOSUMMARY_BLOCK,
+ 'md': RST_AUTOSUMMARY_BLOCK_MARKDOWN
+ },
'converts version changed': {
'rst': '.. versionchanged:: 0.23.0',
'md': '*Changed in 0.23.0*'
@@ -835,10 +992,26 @@
'rst': GRID_TABLE_IN_SKLEARN,
'md': GRID_TABLE_IN_SKLEARN_MARKDOWN
},
+ 'converts broken grid table': {
+ 'rst': BROKEN_GRID_TABLE,
+ 'md': BROKEN_GRID_TABLE_MARKDOWN
+ },
'converts nested parameter lists': {
'rst': NESTED_PARAMETERS,
'md': NESTED_PARAMETERS_MARKDOWN
},
+ 'converts parameter without type': {
+ 'rst': PARAMETERS_WITHOUT_TYPE,
+ 'md': PARAMETERS_WITHOUT_TYPE_MARKDOWN
+ },
+ 'converts indented parameters lists': {
+ 'rst': INDENTED_DOCSTRING,
+ 'md': INDENTED_DOCSTRING_MARKDOWN
+ },
+ 'converts warnings in parameters lists': {
+ 'rst': WARNINGS_IN_PARAMETERS,
+ 'md': WARNINGS_IN_PARAMETERS_MARKDOWN
+ },
'converts sphinx signatures': {
'rst': SPHINX_SIGNATURE,
'md': SPHINX_SIGNATURE_MARKDOWN
@@ -888,3 +1061,10 @@
converted = rst_to_markdown(rst)
print(converted)
assert converted == markdown
+
+
+def test_converter():
+ converter = ReStructuredTextConverter()
+ assert converter.can_convert('.. versionadded:: 0.1')
+ assert not converter.can_convert('this is plain text')
+ assert converter.convert(PEP_287_CODE_BLOCK) == PEP_287_CODE_BLOCK_MARKDOWN