Add basic type stubs (#221) (#215)

* feat: add basic type stubs

* feat: add types for constants

* feat: add type for `MarkdownConverter` class

* ci: add basic job for checking types

* feat: add new constant

* ci: install types as required

* ci: install types package manually

* test: add strict coverage for types

* fix: allow `strip_document` to be `None`

* feat: expand types for MarkdownConverter

* fix: do not use `Unpack` as it requires Python 3.12

* feat: define `MarkdownConverter#convert_soup`

* feat: improve type for `code_language_callback`

* chore: add end-of-file newline

* refactor: use `Union` for now
This commit is contained in:
Gareth Jones
2025-08-03 22:35:46 +12:00
committed by GitHub
parent f7053e46ab
commit 85ef82e083
3 changed files with 166 additions and 0 deletions

View File

@@ -30,3 +30,22 @@ jobs:
- name: Build
run: |
python -m build -nwsx .
types:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install --upgrade setuptools setuptools_scm wheel build tox mypy types-beautifulsoup4
- name: Check types
run: |
mypy .
mypy --strict tests/types.py

77
markdownify/__init__.pyi Normal file
View File

@@ -0,0 +1,77 @@
from _typeshed import Incomplete
from typing import Callable, Union
ATX: str
ATX_CLOSED: str
UNDERLINED: str
SETEXT = UNDERLINED
SPACES: str
BACKSLASH: str
ASTERISK: str
UNDERSCORE: str
LSTRIP: str
RSTRIP: str
STRIP: str
STRIP_ONE: str
def markdownify(
html: str,
autolinks: bool = ...,
bs4_options: str = ...,
bullets: str = ...,
code_language: str = ...,
code_language_callback: Union[Callable[[Incomplete], Union[str, None]], None] = ...,
convert: Union[list[str], None] = ...,
default_title: bool = ...,
escape_asterisks: bool = ...,
escape_underscores: bool = ...,
escape_misc: bool = ...,
heading_style: str = ...,
keep_inline_images_in: list[str] = ...,
newline_style: str = ...,
strip: Union[list[str], None] = ...,
strip_document: Union[str, None] = ...,
strip_pre: str = ...,
strong_em_symbol: str = ...,
sub_symbol: str = ...,
sup_symbol: str = ...,
table_infer_header: bool = ...,
wrap: bool = ...,
wrap_width: int = ...,
) -> str: ...
class MarkdownConverter:
def __init__(
self,
autolinks: bool = ...,
bs4_options: str = ...,
bullets: str = ...,
code_language: str = ...,
code_language_callback: Union[Callable[[Incomplete], Union[str, None]], None] = ...,
convert: Union[list[str], None] = ...,
default_title: bool = ...,
escape_asterisks: bool = ...,
escape_underscores: bool = ...,
escape_misc: bool = ...,
heading_style: str = ...,
keep_inline_images_in: list[str] = ...,
newline_style: str = ...,
strip: Union[list[str], None] = ...,
strip_document: Union[str, None] = ...,
strip_pre: str = ...,
strong_em_symbol: str = ...,
sub_symbol: str = ...,
sup_symbol: str = ...,
table_infer_header: bool = ...,
wrap: bool = ...,
wrap_width: int = ...,
) -> None:
...
def convert(self, html: str) -> str:
...
def convert_soup(self, soup: Incomplete) -> str:
...

70
tests/types.py Normal file
View File

@@ -0,0 +1,70 @@
from markdownify import markdownify, ASTERISK, BACKSLASH, LSTRIP, RSTRIP, SPACES, STRIP, UNDERLINED, UNDERSCORE, MarkdownConverter
from bs4 import BeautifulSoup
from typing import Union
markdownify("<p>Hello</p>") == "Hello" # test default of STRIP
markdownify("<p>Hello</p>", strip_document=LSTRIP) == "Hello\n\n"
markdownify("<p>Hello</p>", strip_document=RSTRIP) == "\n\nHello"
markdownify("<p>Hello</p>", strip_document=STRIP) == "Hello"
markdownify("<p>Hello</p>", strip_document=None) == "\n\nHello\n\n"
# default options
MarkdownConverter(
autolinks=True,
bs4_options='html.parser',
bullets='*+-',
code_language='',
code_language_callback=None,
convert=None,
default_title=False,
escape_asterisks=True,
escape_underscores=True,
escape_misc=False,
heading_style=UNDERLINED,
keep_inline_images_in=[],
newline_style=SPACES,
strip=None,
strip_document=STRIP,
strip_pre=STRIP,
strong_em_symbol=ASTERISK,
sub_symbol='',
sup_symbol='',
table_infer_header=False,
wrap=False,
wrap_width=80,
).convert("")
# custom options
MarkdownConverter(
strip_document=None,
bullets="-",
escape_asterisks=True,
escape_underscores=True,
escape_misc=True,
autolinks=True,
default_title=True,
newline_style=BACKSLASH,
sup_symbol='^',
sub_symbol='^',
keep_inline_images_in=['h3'],
wrap=True,
wrap_width=80,
strong_em_symbol=UNDERSCORE,
code_language='python',
code_language_callback=None
).convert("")
html = '<b>test</b>'
soup = BeautifulSoup(html, 'html.parser')
MarkdownConverter().convert_soup(soup) == '**test**'
def callback(el: BeautifulSoup) -> Union[str, None]:
return el['class'][0] if el.has_attr('class') else None
MarkdownConverter(code_language_callback=callback).convert("")
MarkdownConverter(code_language_callback=lambda el: None).convert("")
markdownify('<pre class="python">test\n foo\nbar</pre>', code_language_callback=callback)
markdownify('<pre class="python">test\n foo\nbar</pre>', code_language_callback=lambda el: None)