From 4aa70de2221a34a3003a7e5f52a9b91965f0e359 Mon Sep 17 00:00:00 2001 From: Spencer Baugh Date: Thu, 23 Sep 2021 09:00:25 -0400 Subject: [PATCH 01/31] TST: use explicit ClassWithNew instead of typing.Generic typing.Generic doesn't have a __new__ method in 3.9. Fixes https://github.com/pdoc3/pdoc/issues/355 --- pdoc/test/__init__.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pdoc/test/__init__.py b/pdoc/test/__init__.py index e8c3d94a..8b67ab77 100644 --- a/pdoc/test/__init__.py +++ b/pdoc/test/__init__.py @@ -1043,16 +1043,20 @@ class C2: self.assertEqual(pdoc.Class('C2', mod, C2).params(), ['a', 'b', 'c=None', '*', 'd=1', 'e']) - class G(typing.Generic[T]): + class ClassWithNew: + def __new__(self, arg): + pass + + class G(ClassWithNew): def __init__(self, a, b, c=100): pass self.assertEqual(pdoc.Class('G', mod, G).params(), ['a', 'b', 'c=100']) - class G2(typing.Generic[T]): + class G2(ClassWithNew): pass - self.assertEqual(pdoc.Class('G2', mod, G2).params(), ['*args', '**kwds']) + self.assertEqual(pdoc.Class('G2', mod, G2).params(), ['arg']) def test_url(self): mod = pdoc.Module(EXAMPLE_MODULE) From 2cce30a9b55eeeddc1ed826c8a2ada53777c3eea Mon Sep 17 00:00:00 2001 From: Howard Smith Date: Thu, 3 Feb 2022 14:42:26 +0000 Subject: [PATCH 02/31] Leave trailing line break in reST directives (#385) * Add to `pdoc.test.Docformats.test_reST_include` to catch issue. * Add a single line-break at the end of `.. include::`ed files - except for when in a code block. * Minor change comment * Revert last 2 commits. * Don't consume trailing newline from reST directives. * [] itself a disjunctive list of characters --- pdoc/html_helpers.py | 6 +++++- pdoc/test/__init__.py | 17 ++++++++++++++++- pdoc/test/example_pkg/_reST_include/table.md | 3 +++ pdoc/test/example_pkg/_reST_include/test.py | 4 ++++ 4 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 pdoc/test/example_pkg/_reST_include/table.md diff --git a/pdoc/html_helpers.py b/pdoc/html_helpers.py index 8ba63db6..51861947 100644 --- a/pdoc/html_helpers.py +++ b/pdoc/html_helpers.py @@ -275,6 +275,9 @@ def _admonition(match, module=None, limit_types=None): if limit_types and type not in limit_types: return match.group(0) + if text is None: + text = "" + if type == 'include' and module: try: return _ToMarkdown._include_file(indent, value, @@ -323,7 +326,8 @@ def admonitions(text, module, limit_types=None): See: https://python-markdown.github.io/extensions/admonition/ """ substitute = partial(re.compile(r'^(?P *)\.\. ?(\w+)::(?: *(.*))?' - r'((?:\n(?:(?P=indent) +.*| *$))*)', re.MULTILINE).sub, + r'((?:\n(?:(?P=indent) +.*| *$))*[^\r\n])*', + re.MULTILINE).sub, partial(_ToMarkdown._admonition, module=module, limit_types=limit_types)) # Apply twice for nested (e.g. image inside warning) diff --git a/pdoc/test/__init__.py b/pdoc/test/__init__.py index 8b67ab77..c98d0c8e 100644 --- a/pdoc/test/__init__.py +++ b/pdoc/test/__init__.py @@ -1568,7 +1568,22 @@ def test_reST_include(self):

1 x = 2 x = 3 -x =

''' +x =

+ + + + + + + + + + + + + +
NameValue
HelloWorld
+

Remaining.

''' mod = pdoc.Module(pdoc.import_module( os.path.join(TESTS_BASEDIR, EXAMPLE_MODULE, '_reST_include', 'test.py'))) html = to_html(mod.docstring, module=mod) diff --git a/pdoc/test/example_pkg/_reST_include/table.md b/pdoc/test/example_pkg/_reST_include/table.md new file mode 100644 index 00000000..abf0fc4f --- /dev/null +++ b/pdoc/test/example_pkg/_reST_include/table.md @@ -0,0 +1,3 @@ +| Name | Value | +| ----- | ----- | +| Hello | World | diff --git a/pdoc/test/example_pkg/_reST_include/test.py b/pdoc/test/example_pkg/_reST_include/test.py index 96c25c41..acf75900 100644 --- a/pdoc/test/example_pkg/_reST_include/test.py +++ b/pdoc/test/example_pkg/_reST_include/test.py @@ -8,4 +8,8 @@ .. include:: foo/../_include_me.py :start-after: = :end-before: 4 + +.. include:: table.md + +Remaining. """ From 60f77d2e6c033f62be8a9351c863246e5f88b4a1 Mon Sep 17 00:00:00 2001 From: Abhinav Singh <126065+abhinavsingh@users.noreply.github.com> Date: Fri, 19 Aug 2022 20:06:32 +0530 Subject: [PATCH 03/31] BUG: Skip `__editable__` paths during `iter_modules` (#408) * Skip `__editable__` paths during `iter_modules` * Update pdoc/__init__.py --- pdoc/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pdoc/__init__.py b/pdoc/__init__.py index 0f05b2c0..8524291e 100644 --- a/pdoc/__init__.py +++ b/pdoc/__init__.py @@ -727,6 +727,9 @@ def iter_modules(paths): """ from os.path import isdir, join for pth in paths: + if pth.startswith("__editable__."): + # See https://github.com/pypa/pip/issues/11380 + continue for file in os.listdir(pth): if file.startswith(('.', '__pycache__', '__init__.py')): continue From fce8d989748fc868f215882ed2f7f2829b588ce8 Mon Sep 17 00:00:00 2001 From: Kernc Date: Sun, 3 Apr 2022 21:27:59 +0200 Subject: [PATCH 04/31] BUG: Remove extra preceding newlines in markdown listing --- pdoc/templates/text.mako | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pdoc/templates/text.mako b/pdoc/templates/text.mako index 720a59cd..35e5eeda 100644 --- a/pdoc/templates/text.mako +++ b/pdoc/templates/text.mako @@ -12,21 +12,21 @@ <%def name="function(func)" buffered="True"> - <% +<% returns = show_type_annotations and func.return_annotation() or '' if returns: returns = ' \N{non-breaking hyphen}> ' + returns - %> +%> `${func.name}(${", ".join(func.params(annotate=show_type_annotations))})${returns}` ${func.docstring | deflist} <%def name="variable(var)" buffered="True"> - <% +<% annot = show_type_annotations and var.type_annotation() or '' if annot: annot = ': ' + annot - %> +%> `${var.name}${annot}` ${var.docstring | deflist} From b3a56d9e8628554f24cf31554e436079e1555341 Mon Sep 17 00:00:00 2001 From: Kernc Date: Sat, 29 Oct 2022 18:30:43 +0200 Subject: [PATCH 05/31] CI: Fix mypy warning --- pdoc/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pdoc/__init__.py b/pdoc/__init__.py index 8524291e..41f4fb61 100644 --- a/pdoc/__init__.py +++ b/pdoc/__init__.py @@ -533,7 +533,7 @@ def __init__(self, name: str, module, obj, docstring: str = None): def __repr__(self): return f'<{self.__class__.__name__} {self.refname!r}>' - @property # type: ignore + @property @lru_cache() def source(self) -> str: """ From 80af5d40d3ca39e2701c44941c1003ae6a280799 Mon Sep 17 00:00:00 2001 From: Kernc Date: Sat, 29 Oct 2022 18:55:46 +0200 Subject: [PATCH 06/31] CI: Bump min Python 3.7+ and update tests for Python 3.10 Fix https://github.com/pdoc3/pdoc/issues/400 Thanks @tjni --- .github/workflows/ci.yml | 6 +++--- pdoc/__init__.py | 2 +- pdoc/documentation.md | 2 +- pdoc/test/__init__.py | 23 ++++++++++++++++++----- setup.py | 6 +++--- 5 files changed, 26 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 75b394f6..238673a2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,11 +11,11 @@ jobs: strategy: matrix: - python-version: [3.6, 3.7] + python-version: [3.7, 3.8, '3.10'] include: - - python-version: 3.8 + - python-version: 3.9 test-type: lint - - python-version: 3.8 + - python-version: 3.9 test-type: docs steps: diff --git a/pdoc/__init__.py b/pdoc/__init__.py index 41f4fb61..c1e784ee 100644 --- a/pdoc/__init__.py +++ b/pdoc/__init__.py @@ -1275,7 +1275,7 @@ def _formatannotation(annot): `typing.Optional`, `nptyping.NDArray` and other types. >>> _formatannotation(NewType('MyType', str)) - 'MyType' + 'pdoc.MyType' >>> _formatannotation(Optional[Tuple[Optional[int], None]]) 'Optional[Tuple[Optional[int], None]]' """ diff --git a/pdoc/documentation.md b/pdoc/documentation.md index 57e1a002..e6b477b3 100644 --- a/pdoc/documentation.md +++ b/pdoc/documentation.md @@ -353,7 +353,7 @@ modified templates into the `directories` list of the Compatibility ------------- -`pdoc` requires Python 3.6+. +`pdoc` requires Python 3.7+. The last version to support Python 2.x is [pdoc3 0.3.x]. [pdoc3 0.3.x]: https://pypi.org/project/pdoc3/0.3.13/ diff --git a/pdoc/test/__init__.py b/pdoc/test/__init__.py index c98d0c8e..6b59f0ed 100644 --- a/pdoc/test/__init__.py +++ b/pdoc/test/__init__.py @@ -126,7 +126,8 @@ class CliTest(unittest.TestCase): def setUp(self): pdoc.reset() - @unittest.skipIf(sys.version_info < (3, 7), 'pdoc._formatannotation fails on Py3.6') + @unittest.skipIf(sys.version_info < (3, 10), + 'HACK: _formatannotation() changed return value in Py3.10') def test_project_doctests(self): doctests = doctest.testmod(pdoc) assert not doctests.failed and doctests.attempted, doctests @@ -185,8 +186,12 @@ def test_html(self): '_private', ' class="ident">_Private', - 'non_callable_routine', ] + if sys.version_info >= (3, 10): + include_patterns.append('non_callable_routine') + else: + exclude_patterns.append('non_callable_routine') + package_files = { '': self.PUBLIC_FILES, '.subpkg2': [f for f in self.PUBLIC_FILES @@ -356,8 +361,11 @@ def test_text(self): '_Private', 'subprocess', 'Hidden', - 'non_callable_routine', ] + if sys.version_info >= (3, 10): + include_patterns.append('non_callable_routine') + else: + exclude_patterns.append('non_callable_routine') with self.subTest(package=EXAMPLE_MODULE): with redirect_streams() as (stdout, _): @@ -543,8 +551,9 @@ class C: self.assertEqual(doc.doc['vars_dont'].docstring, '') self.assertIn('integer', doc.doc['but_clss_have_doc'].docstring) + @unittest.skipIf(sys.version_info >= (3, 10), 'No builtin module "parser" in Py3.10') def test_builtin_methoddescriptors(self): - import parser + import parser # TODO: replace with another public binary builtin with self.assertWarns(UserWarning): c = pdoc.Class('STType', pdoc.Module(parser), parser.STType) self.assertIsInstance(c.doc['compile'], pdoc.Function) @@ -906,9 +915,13 @@ def bug130_str_annotation(a: "str"): def bug253_newtype_annotation(a: CustomType): return + expected = CustomType.__name__ + if sys.version_info > (3, 10): + expected = f'{__name__}.{CustomType.__name__}' + self.assertEqual( pdoc.Function('bug253', mod, bug253_newtype_annotation).params(annotate=True), - ['a:\N{NBSP}CustomType']) + [f'a:\N{NBSP}{expected}']) # typing.Callable bug def f(a: typing.Callable): diff --git a/setup.py b/setup.py index d5372303..1902b927 100644 --- a/setup.py +++ b/setup.py @@ -2,8 +2,8 @@ import sys from setuptools import setup, find_packages -if sys.version_info < (3, 6): - sys.exit('ERROR: pdoc requires Python 3.6+') +if sys.version_info < (3, 7): + sys.exit('ERROR: pdoc requires Python 3.7+') def _discover_tests(): @@ -58,5 +58,5 @@ def _discover_tests(): 'write_to': os.path.join('pdoc', '_version.py'), }, test_suite="setup._discover_tests", - python_requires='>= 3.6', + python_requires='>= 3.7', ) From 3ecfbcfb658c5be9ee6ab572b63db2cb5e1c29e1 Mon Sep 17 00:00:00 2001 From: kernc Date: Wed, 21 Dec 2022 04:55:00 +0100 Subject: [PATCH 07/31] DOC: Fix Shields.io build status badge Ref: https://github.com/badges/shields/issues/8671 --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 8afc1953..3de1b92f 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,7 @@ pdoc ==== - -[![Build Status](https://img.shields.io/github/workflow/status/pdoc3/pdoc/CI?style=for-the-badge)](https://github.com/pdoc3/pdoc/actions) +[![Build Status](https://img.shields.io/github/actions/workflow/status/pdoc3/pdoc/ci.yml?branch=master&style=for-the-badge)](https://github.com/pdoc3/pdoc/actions) [![Code Coverage](https://img.shields.io/codecov/c/gh/pdoc3/pdoc.svg?style=for-the-badge)](https://codecov.io/gh/pdoc3/pdoc) [![pdoc3 on PyPI](https://img.shields.io/pypi/v/pdoc3.svg?color=blue&style=for-the-badge)](https://pypi.org/project/pdoc3) [![package downloads](https://img.shields.io/pypi/dm/pdoc3.svg?color=skyblue&style=for-the-badge)](https://pypi.org/project/pdoc3) From 14cd51c1b7431cdec5c3e7510b8a0e3b66c2f7d4 Mon Sep 17 00:00:00 2001 From: Henrik Seidel Date: Sat, 9 Mar 2024 05:55:19 +0100 Subject: [PATCH 08/31] REF: Fix deprecation warnings for PEP224 docstrings of class variables (#437) * Update __init__.py ast.Str is deprecated and will be removed in Python 3.14; use ast.Constant instead * Update __init__.py Attribute s is deprecated and will be removed in Python 3.14; use value instead * Update __init__.py DeprecationWarning: Attribute s is deprecated and will be removed in Python 3.14; use value instead --- pdoc/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pdoc/__init__.py b/pdoc/__init__.py index c1e784ee..84e06a4e 100644 --- a/pdoc/__init__.py +++ b/pdoc/__init__.py @@ -316,14 +316,14 @@ def get_name(assign_node): for assign_node, str_node in _pairwise(ast.iter_child_nodes(tree)): if not (isinstance(assign_node, (ast.Assign, ast.AnnAssign)) and isinstance(str_node, ast.Expr) and - isinstance(str_node.value, ast.Str)): + isinstance(str_node.value, ast.Constant)): continue name = get_name(assign_node) if not name: continue - docstring = inspect.cleandoc(str_node.value.s).strip() + docstring = inspect.cleandoc(str_node.value.value).strip() if not docstring: continue From bf256c6b29e0a3fcaee6e78a63d5ac5e32d82959 Mon Sep 17 00:00:00 2001 From: Kernc Date: Thu, 24 Aug 2023 04:18:13 +0200 Subject: [PATCH 09/31] MNT: Replace GAnalytics tracking code --- doc/build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/build.sh b/doc/build.sh index 044101c5..a8cb3ca8 100755 --- a/doc/build.sh +++ b/doc/build.sh @@ -29,9 +29,9 @@ popd >/dev/null if [ "$IS_RELEASE" ]; then echo -e '\nAdding GAnalytics code\n' - ANALYTICS="" + ANALYTICS="" find "$BUILDROOT" -name '*.html' -print0 | - xargs -0 -- sed -i "s##$ANALYTICS#i" + xargs -0 -- sed -i "s##$ANALYTICS#i" ANALYTICS='' find "$BUILDROOT" -name '*.html' -print0 | xargs -0 -- sed -i "s##$ANALYTICS#i" From 8ce42f0d899e70016fea7108b491e796949b7fd8 Mon Sep 17 00:00:00 2001 From: Kernc Date: Thu, 24 Aug 2023 04:18:56 +0200 Subject: [PATCH 10/31] ENH: Support Google Analytics 4 tracking --- pdoc/templates/config.mako | 4 ++-- pdoc/templates/html.mako | 9 ++++++--- pdoc/test/__init__.py | 4 ++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/pdoc/templates/config.mako b/pdoc/templates/config.mako index 992ff2eb..f5e4d476 100644 --- a/pdoc/templates/config.mako +++ b/pdoc/templates/config.mako @@ -38,8 +38,8 @@ # Demo: https://highlightjs.org/static/demo/ hljs_style = 'github' - # If set, insert Google Analytics tracking code. Value is GA - # tracking id (UA-XXXXXX-Y). + # If set, insert Google Analytics 4 tracking code. Value is GA + # tracking id (G-XXXXXXXXXX). google_analytics = '' # If set, insert Google Custom Search search bar widget above the sidebar index. diff --git a/pdoc/templates/html.mako b/pdoc/templates/html.mako index f0517880..7964ec92 100644 --- a/pdoc/templates/html.mako +++ b/pdoc/templates/html.mako @@ -392,10 +392,13 @@ % if google_analytics: + + window.dataLayer = window.dataLayer || []; + function gtag(){dataLayer.push(arguments);} + gtag('js', new Date()); + gtag('config', '${google_analytics}'); + % endif % if google_search_query: diff --git a/pdoc/test/__init__.py b/pdoc/test/__init__.py index 6b59f0ed..95601f94 100644 --- a/pdoc/test/__init__.py +++ b/pdoc/test/__init__.py @@ -433,10 +433,10 @@ def test_output_text(self): for file in self.PUBLIC_FILES]) def test_google_analytics(self): - expected = ['google-analytics.com'] + expected = ['googletagmanager.com'] with run_html(EXAMPLE_MODULE): self._check_files((), exclude_patterns=expected) - with run_html(EXAMPLE_MODULE, config='google_analytics="UA-xxxxxx-y"'): + with run_html(EXAMPLE_MODULE, config='google_analytics="G-xxxxxxxxxx"'): self._check_files(expected) def test_relative_dir_path(self): From 65459eab0b2c25cc87dc4c33328eb7c620068c26 Mon Sep 17 00:00:00 2001 From: Kernc Date: Sat, 9 Mar 2024 05:08:31 +0100 Subject: [PATCH 11/31] CI: Bump tested Python version and actions versions --- .github/workflows/ci.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 238673a2..24b21d16 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,36 +2,36 @@ name: CI on: push: { branches: [master] } pull_request: { branches: [master] } - schedule: [ cron: '12 2 * * 6' ] # Every Saturday, 02:12 + schedule: [ cron: '12 2 6 * *' ] jobs: build: name: Build - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest strategy: matrix: - python-version: [3.7, 3.8, '3.10'] + python-version: [3.11, '>=3'] include: - - python-version: 3.9 + - python-version: 3.11 test-type: lint - - python-version: 3.9 + - python-version: 3.11 test-type: docs steps: - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - - uses: actions/cache@v2 + - uses: actions/cache@v4 name: Set up caches with: path: ~/.cache/pip key: ${{ runner.os }}-py${{ matrix.python-version }} - name: Checkout repo - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: fetch-depth: 3 - name: Fetch tags From 75d2c5f3d9c84f3cc3a64e129c1685f767d7ed78 Mon Sep 17 00:00:00 2001 From: Kernc Date: Sat, 9 Mar 2024 05:47:46 +0100 Subject: [PATCH 12/31] MNT: Untangle CI workflow into separate jobs --- .github/actions/setup/action.yml | 21 +++++++++ .github/workflows/ci.yml | 81 +++++++++++++------------------- README.md | 2 +- 3 files changed, 55 insertions(+), 49 deletions(-) create mode 100644 .github/actions/setup/action.yml diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml new file mode 100644 index 00000000..c00bc5ed --- /dev/null +++ b/.github/actions/setup/action.yml @@ -0,0 +1,21 @@ +name: 'Default Checkout' +description: 'checkout & setup' +inputs: + python-version: + description: 'Python version' + required: true + default: '>=3' +runs: + using: "composite" + steps: + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ inputs.python-version }} + - uses: actions/cache@v4 + with: + path: | + ~/.cache/pip + ~\AppData\Local\pip\Cache + ~/Library/Caches/pip + key: ${{ runner.os }}-py${{ inputs.python-version }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 24b21d16..17db5a0c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,69 +5,54 @@ on: schedule: [ cron: '12 2 6 * *' ] jobs: - build: - name: Build + test-matrix: runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/setup + with: + python-version: '>=3' - strategy: - matrix: - python-version: [3.11, '>=3'] - include: - - python-version: 3.11 - test-type: lint - - python-version: 3.11 - test-type: docs + - run: pip install -U pip setuptools wheel && pip install -U . + - run: time python -m unittest -v pdoc.test + lint-test-coverage: + runs-on: ubuntu-latest steps: - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + - uses: actions/checkout@v4 with: - python-version: ${{ matrix.python-version }} - - - uses: actions/cache@v4 - name: Set up caches + fetch-depth: 2 # For codecov + - uses: ./.github/actions/setup with: - path: ~/.cache/pip - key: ${{ runner.os }}-py${{ matrix.python-version }} + python-version: 3.11 - - name: Checkout repo - uses: actions/checkout@v4 - with: - fetch-depth: 3 - - name: Fetch tags - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - - - name: Install dependencies - run: | - pip install -U pip setuptools wheel - pip install -U . + - run: pip install -U pip setuptools wheel && pip install -U . - name: Install lint dependencies - if: matrix.test-type == 'lint' run: | pip install flake8 coverage mypy types-Markdown sudo apt update && sudo apt-get install \ texlive-xetex lmodern texlive-fonts-recommended # test_pdf_pandoc wget -O/tmp/pandoc.deb https://github.com/jgm/pandoc/releases/download/2.10/pandoc-2.10-1-amd64.deb && sudo dpkg -i /tmp/pandoc.deb - - name: Install docs dependencies - if: matrix.test-type == 'docs' - run: pip install -e . + - run: find -name '*.md' | xargs .github/lint-markdown.sh + - run: flake8 + - run: mypy -p pdoc + - run: time coverage run -m unittest -v pdoc.test + - run: bash <(curl -s https://codecov.io/bash) + - run: coverage report + - run: PDOC_TEST_PANDOC=1 time python -m unittest -v pdoc.test.CliTest.test_pdf_pandoc - - name: Test w/ Coverage, Lint - if: matrix.test-type == 'lint' - run: | - find -name '*.md' | xargs .github/lint-markdown.sh - flake8 - mypy -p pdoc - time coverage run -m unittest -v pdoc.test - PDOC_TEST_PANDOC=1 time catchsegv python -m unittest -v pdoc.test.CliTest.test_pdf_pandoc - bash <(curl -s https://codecov.io/bash) + docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/setup + with: + python-version: 3.11 - - name: Test - if: '! matrix.test-type' - run: time python -m unittest -v pdoc.test + - name: Fetch tags + run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - - name: Test docs - if: matrix.test-type == 'docs' - run: time doc/build.sh + - run: pip install -U pip setuptools wheel && pip install -e . + - run: time doc/build.sh \ No newline at end of file diff --git a/README.md b/README.md index 3de1b92f..55eca269 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ pdoc [![package downloads](https://img.shields.io/pypi/dm/pdoc3.svg?color=skyblue&style=for-the-badge)](https://pypi.org/project/pdoc3) [![GitHub Sponsors](https://img.shields.io/github/sponsors/kernc?color=pink&style=for-the-badge)](https://github.com/sponsors/kernc) -Auto-generate API documentation for Python projects. +Auto-generate API documentation for Python 3+ projects. [**Project website**](https://pdoc3.github.io/pdoc/) From da13281edcee60329867438d2e57a2ac66a51178 Mon Sep 17 00:00:00 2001 From: Kernc Date: Sat, 9 Mar 2024 06:02:22 +0100 Subject: [PATCH 13/31] MNT: Fix flake8 errors --- .github/workflows/ci.yml | 2 +- pdoc/cli.py | 3 ++- pdoc/test/__init__.py | 12 ++++++------ 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 17db5a0c..2b97b21c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: wget -O/tmp/pandoc.deb https://github.com/jgm/pandoc/releases/download/2.10/pandoc-2.10-1-amd64.deb && sudo dpkg -i /tmp/pandoc.deb - run: find -name '*.md' | xargs .github/lint-markdown.sh - - run: flake8 + - run: flake8 pdoc setup.py - run: mypy -p pdoc - run: time coverage run -m unittest -v pdoc.test - run: bash <(curl -s https://codecov.io/bash) diff --git a/pdoc/cli.py b/pdoc/cli.py index e1895819..5fdba07c 100755 --- a/pdoc/cli.py +++ b/pdoc/cli.py @@ -524,12 +524,13 @@ def main(_args=None): httpd.server_close() sys.exit(0) - docfilter = None if args.filter and args.filter.strip(): def docfilter(obj, _filters=args.filter.strip().split(',')): return any(f in obj.refname or isinstance(obj, pdoc.Class) and f in obj.doc for f in _filters) + else: + docfilter = None modules = [pdoc.Module(module, docfilter=docfilter, skip_errors=args.skip_errors) diff --git a/pdoc/test/__init__.py b/pdoc/test/__init__.py index 95601f94..01681db7 100644 --- a/pdoc/test/__init__.py +++ b/pdoc/test/__init__.py @@ -250,13 +250,13 @@ def test_html_ref_links(self): ) def test_docformat(self): - with self.assertWarns(UserWarning) as cm,\ + with self.assertWarns(UserWarning) as cm, \ run_html(EXAMPLE_MODULE, config='docformat="restructuredtext"'): self._basic_html_assertions() self.assertIn('numpy', cm.warning.args[0]) def test_html_no_source(self): - with self.assertWarns(DeprecationWarning),\ + with self.assertWarns(DeprecationWarning), \ run_html(EXAMPLE_MODULE, html_no_source=None): self._basic_html_assertions() self._check_files(exclude_patterns=['class="source"', 'Hidden']) @@ -301,7 +301,7 @@ def test_external_links(self): self._basic_html_assertions() self._check_files(exclude_patterns=[' Date: Sat, 9 Mar 2024 06:17:45 +0100 Subject: [PATCH 14/31] MNT: Fix mypy errors --- pdoc/__init__.py | 25 ++++++++++++++----------- pdoc/html_helpers.py | 12 +++++++----- pdoc/test/__init__.py | 8 ++++---- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/pdoc/__init__.py b/pdoc/__init__.py index 84e06a4e..4e367378 100644 --- a/pdoc/__init__.py +++ b/pdoc/__init__.py @@ -267,7 +267,7 @@ def _pep224_docstrings(doc_obj: Union['Module', 'Class'], *, # Maybe raise exceptions with appropriate message # before using cleaned doc_obj.source _ = inspect.findsource(doc_obj.obj) - tree = ast.parse(doc_obj.source) # type: ignore + tree = ast.parse(doc_obj.source) except (OSError, TypeError, SyntaxError) as exc: # Don't emit a warning for builtins that don't have source available is_builtin = getattr(doc_obj.obj, '__module__', None) == 'builtins' @@ -345,7 +345,7 @@ def get_name(assign_node): def get_indent(line): return len(line) - len(line.lstrip()) - source_lines = doc_obj.source.splitlines() # type: ignore + source_lines = doc_obj.source.splitlines() assign_line = source_lines[assign_node.lineno - 1] assign_indent = get_indent(assign_line) comment_lines = [] @@ -451,7 +451,7 @@ def _toposort(graph: Mapping[T, Set[T]]) -> Generator[T, None, None]: assert not graph, f"A cyclic dependency exists amongst {graph!r}" -def link_inheritance(context: Context = None): +def link_inheritance(context: Optional[Context] = None): """ Link inheritance relationsships between `pdoc.Class` objects (and between their members) of all `pdoc.Module` objects that @@ -491,7 +491,7 @@ class Doc: """ __slots__ = ('module', 'name', 'obj', 'docstring', 'inherits') - def __init__(self, name: str, module, obj, docstring: str = None): + def __init__(self, name: str, module, obj, docstring: str = ''): """ Initializes a documentation object, where `name` is the public identifier name, `module` is a `pdoc.Module` object where raw @@ -566,7 +566,7 @@ def qualname(self) -> str: return getattr(self.obj, '__qualname__', self.name) @lru_cache() - def url(self, relative_to: 'Module' = None, *, link_prefix: str = '', + def url(self, relative_to: Optional['Module'] = None, *, link_prefix: str = '', top_ancestor: bool = False) -> str: """ Canonical relative URL (including page fragment) for this @@ -624,8 +624,10 @@ class Module(Doc): __slots__ = ('supermodule', 'doc', '_context', '_is_inheritance_linked', '_skipped_submodules') - def __init__(self, module: Union[ModuleType, str], *, docfilter: Callable[[Doc], bool] = None, - supermodule: 'Module' = None, context: Context = None, + def __init__(self, module: Union[ModuleType, str], *, + docfilter: Optional[Callable[[Doc], bool]] = None, + supermodule: Optional['Module'] = None, + context: Optional[Context] = None, skip_errors: bool = False): """ Creates a `Module` documentation object given the actual @@ -1010,7 +1012,7 @@ class Class(Doc): """ __slots__ = ('doc', '_super_members') - def __init__(self, name: str, module: Module, obj, *, docstring: str = None): + def __init__(self, name: str, module: Module, obj, *, docstring: Optional[str] = None): assert inspect.isclass(obj) if docstring is None: @@ -1317,7 +1319,7 @@ class Function(Doc): """ __slots__ = ('cls',) - def __init__(self, name: str, module: Module, obj, *, cls: Class = None): + def __init__(self, name: str, module: Module, obj, *, cls: Optional[Class] = None): """ Same as `pdoc.Doc`, except `obj` must be a Python function object. The docstring is gathered automatically. @@ -1419,7 +1421,8 @@ def return_annotation(self, *, link=None) -> str: s = re.sub(r'[\w\.]+', partial(_linkify, link=link, module=self.module), s) return s - def params(self, *, annotate: bool = False, link: Callable[[Doc], str] = None) -> List[str]: + def params(self, *, annotate: bool = False, + link: Optional[Callable[[Doc], str]] = None) -> List[str]: """ Returns a list where each element is a nicely formatted parameter of this function. This includes argument lists, @@ -1589,7 +1592,7 @@ class Variable(Doc): __slots__ = ('cls', 'instance_var') def __init__(self, name: str, module: Module, docstring, *, - obj=None, cls: Class = None, instance_var: bool = False): + obj=None, cls: Optional[Class] = None, instance_var: bool = False): """ Same as `pdoc.Doc`, except `cls` should be provided as a `pdoc.Class` object when this is a class or instance diff --git a/pdoc/html_helpers.py b/pdoc/html_helpers.py index 51861947..51f7336d 100644 --- a/pdoc/html_helpers.py +++ b/pdoc/html_helpers.py @@ -9,7 +9,7 @@ import traceback from contextlib import contextmanager from functools import partial, lru_cache -from typing import Callable, Match +from typing import Callable, Match, Optional from warnings import warn import xml.etree.ElementTree as etree @@ -408,8 +408,9 @@ def handleMatch(self, m, data): def to_html(text: str, *, - docformat: str = None, - module: pdoc.Module = None, link: Callable[..., str] = None, + docformat: Optional[str] = None, + module: Optional[pdoc.Module] = None, + link: Optional[Callable[..., str]] = None, latex_math: bool = False): """ Returns HTML of `text` interpreted as `docformat`. `__docformat__` is respected @@ -433,8 +434,9 @@ def to_html(text: str, *, def to_markdown(text: str, *, - docformat: str = None, - module: pdoc.Module = None, link: Callable[..., str] = None): + docformat: Optional[str] = None, + module: Optional[pdoc.Module] = None, + link: Optional[Callable[..., str]] = None): """ Returns `text`, assumed to be a docstring in `docformat`, converted to markdown. `__docformat__` is respected diff --git a/pdoc/test/__init__.py b/pdoc/test/__init__.py index 01681db7..e1de7625 100644 --- a/pdoc/test/__init__.py +++ b/pdoc/test/__init__.py @@ -70,10 +70,10 @@ def run(*args, **kwargs) -> int: params = list(filter(None, chain.from_iterable(params))) # type: ignore _args = cli.parser.parse_args([*params, *args]) # type: ignore try: - returncode = cli.main(_args) - return returncode or 0 + cli.main(_args) + return 0 except SystemExit as e: - return e.code + return bool(e.code) @contextmanager @@ -953,7 +953,7 @@ def test_test_Function_params_python38_specific(self): self.assertEqual(func.params(), ['a', '/']) def test_Function_return_annotation(self): - def f() -> typing.List[typing.Union[str, pdoc.Doc]]: pass + def f() -> typing.List[typing.Union[str, pdoc.Doc]]: return [] func = pdoc.Function('f', DUMMY_PDOC_MODULE, f) self.assertEqual(func.return_annotation(), 'List[Union[str,\N{NBSP}pdoc.Doc]]') From 641e7a85307a3bcd83142ce49cb11dacdc50f221 Mon Sep 17 00:00:00 2001 From: Kernc Date: Sat, 9 Mar 2024 14:38:58 +0100 Subject: [PATCH 15/31] CI: Bump tested pandoc integration from v2 to v3 --- .github/workflows/ci.yml | 6 +++++- pdoc/cli.py | 2 +- pdoc/test/__init__.py | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2b97b21c..7ad3f6f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: pip install flake8 coverage mypy types-Markdown sudo apt update && sudo apt-get install \ texlive-xetex lmodern texlive-fonts-recommended # test_pdf_pandoc - wget -O/tmp/pandoc.deb https://github.com/jgm/pandoc/releases/download/2.10/pandoc-2.10-1-amd64.deb && sudo dpkg -i /tmp/pandoc.deb + wget -O/tmp/pandoc.deb https://github.com/jgm/pandoc/releases/download/3.1.12.2/pandoc-3.1.12.2-1-amd64.deb && sudo dpkg -i /tmp/pandoc.deb - run: find -name '*.md' | xargs .github/lint-markdown.sh - run: flake8 pdoc setup.py @@ -42,6 +42,10 @@ jobs: - run: bash <(curl -s https://codecov.io/bash) - run: coverage report - run: PDOC_TEST_PANDOC=1 time python -m unittest -v pdoc.test.CliTest.test_pdf_pandoc + - uses: actions/upload-artifact@v3 + with: + name: Pdoc Documentation.pdf + path: /tmp/pdoc.pdf docs: runs-on: ubuntu-latest diff --git a/pdoc/cli.py b/pdoc/cli.py index 5fdba07c..85a78ab7 100755 --- a/pdoc/cli.py +++ b/pdoc/cli.py @@ -593,7 +593,7 @@ def docfilter(obj, _filters=args.filter.strip().split(',')): pandoc --metadata=title:"MyProject Documentation" \\ --from=markdown+abbreviations+tex_math_single_backslash \\ --pdf-engine=xelatex --variable=mainfont:"DejaVu Sans" \\ - --toc --toc-depth=4 --output=pdf.pdf pdf.md\ + --toc --toc-depth=4 --output=/tmp/pdoc.pdf pdf.md ''' diff --git a/pdoc/test/__init__.py b/pdoc/test/__init__.py index e1de7625..0e122a7b 100644 --- a/pdoc/test/__init__.py +++ b/pdoc/test/__init__.py @@ -417,7 +417,7 @@ def test_pdf_pandoc(self): run('pdoc', pdf=None) f.write(stdout.getvalue()) subprocess.run(pdoc.cli._PANDOC_COMMAND, shell=True, check=True) - self.assertTrue(os.path.exists('pdf.pdf')) + self.assertTrue(os.path.exists('/tmp/pdoc.pdf')) def test_config(self): with run_html(EXAMPLE_MODULE, config='link_prefix="/foobar/"'): From 96e915575d0a9f611d254d3b853f38c0ca59f32f Mon Sep 17 00:00:00 2001 From: John McCann Cunniff Jr Date: Fri, 22 Dec 2023 17:09:25 -0500 Subject: [PATCH 16/31] BUG: Strengthen signature detection for pybind generated modules --- pdoc/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pdoc/__init__.py b/pdoc/__init__.py index 4e367378..0f4e2c67 100644 --- a/pdoc/__init__.py +++ b/pdoc/__init__.py @@ -1570,7 +1570,7 @@ def _signature_from_string(self): try: exec(f'def {string}: pass', _globals, _locals) - except SyntaxError: + except Exception: continue signature = inspect.signature(_locals[self.name]) if cleanup_docstring and len(strings) == 1: From cf5bffdb89b32e060ff8dc08a6a78c7c4af67290 Mon Sep 17 00:00:00 2001 From: frank101010 <52856343+frank101010@users.noreply.github.com> Date: Mon, 11 Mar 2024 04:39:49 +0100 Subject: [PATCH 17/31] BUG: Added UnicodeDecodeError to list of expected exceptions in _pep224_docstrings() (#396) Co-authored-by: Frank Pecher --- pdoc/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pdoc/__init__.py b/pdoc/__init__.py index 0f4e2c67..d2743e8e 100644 --- a/pdoc/__init__.py +++ b/pdoc/__init__.py @@ -268,7 +268,7 @@ def _pep224_docstrings(doc_obj: Union['Module', 'Class'], *, # before using cleaned doc_obj.source _ = inspect.findsource(doc_obj.obj) tree = ast.parse(doc_obj.source) - except (OSError, TypeError, SyntaxError) as exc: + except (OSError, TypeError, SyntaxError, UnicodeDecodeError) as exc: # Don't emit a warning for builtins that don't have source available is_builtin = getattr(doc_obj.obj, '__module__', None) == 'builtins' if not is_builtin: From 56bbc9c20296054f2d41e09776bf723dd4f88274 Mon Sep 17 00:00:00 2001 From: Terrance Date: Sat, 22 Jun 2024 00:46:22 +0100 Subject: [PATCH 18/31] BUG: Fix documenting classes that contain `unittest.mock.Mock` (#352) * BUG: Make `unittest.mock.Mock` not appear callable (fix #350) * Add class with mocks to unit test example package --- pdoc/__init__.py | 3 ++- pdoc/test/__init__.py | 10 +++++++++- pdoc/test/example_pkg/__init__.py | 17 +++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/pdoc/__init__.py b/pdoc/__init__.py index d2743e8e..3ceb3ee1 100644 --- a/pdoc/__init__.py +++ b/pdoc/__init__.py @@ -26,6 +26,7 @@ cast, Any, Callable, Dict, Generator, Iterable, List, Mapping, NewType, Optional, Set, Tuple, Type, TypeVar, Union, ) +from unittest.mock import Mock from warnings import warn from mako.lookup import TemplateLookup @@ -410,7 +411,7 @@ def _is_public(ident_name): def _is_function(obj): - return inspect.isroutine(obj) and callable(obj) + return inspect.isroutine(obj) and callable(obj) and not isinstance(obj, Mock) # Mock: GH-350 def _is_descriptor(obj): diff --git a/pdoc/test/__init__.py b/pdoc/test/__init__.py index 0e122a7b..4a488c55 100644 --- a/pdoc/test/__init__.py +++ b/pdoc/test/__init__.py @@ -22,7 +22,8 @@ from tempfile import TemporaryDirectory from time import sleep from types import ModuleType -from unittest.mock import patch +from unittest import expectedFailure +from unittest.mock import Mock, patch from urllib.error import HTTPError from urllib.request import Request, urlopen @@ -355,6 +356,8 @@ def test_text(self): 'B.p docstring', 'C', 'B.overridden docstring', + 'function_mock', + 'coroutine_mock', ] exclude_patterns = [ '_private', @@ -1174,6 +1177,11 @@ def __init__(self): self.assertEqual(mod.doc['C'].doc['class_var'].docstring, 'class var') self.assertEqual(mod.doc['C'].doc['instance_var'].docstring, 'instance var') + @expectedFailure + def test_mock_signature_error(self): + # GH-350 -- throws `TypeError: 'Mock' object is not subscriptable`: + self.assertIsInstance(inspect.signature(Mock(spec=lambda x: x)), inspect.Signature) + class HtmlHelpersTest(unittest.TestCase): """ diff --git a/pdoc/test/example_pkg/__init__.py b/pdoc/test/example_pkg/__init__.py index 4a8aeef9..e99a8213 100644 --- a/pdoc/test/example_pkg/__init__.py +++ b/pdoc/test/example_pkg/__init__.py @@ -2,6 +2,7 @@ from collections import namedtuple import subprocess import os +from unittest.mock import AsyncMock, Mock CONST = 'const' """CONST docstring""" @@ -363,3 +364,19 @@ def latex_math(): class Location(namedtuple('Location', 'lat lon')): """Geo-location, GPS position.""" + + +def _func_spec(value: int) -> bool: + ... + +async def _coro_spec(value: int) -> bool: + ... + + +class HasMockAttributes: + """ + Test class containing instances of `unittest.mock.Mock`. + """ + + function_mock = Mock(spec=_func_spec) + coroutine_mock = AsyncMock(spec=_coro_spec) From 2c75136edeb122b4929fff66aa2933ce50ad8608 Mon Sep 17 00:00:00 2001 From: Kernc Date: Tue, 12 Mar 2024 20:28:49 +0100 Subject: [PATCH 19/31] ENH: Support MathJax inline $dollar-pattern$ Fixes https://github.com/pdoc3/pdoc/issues/410 --- pdoc/templates/html.mako | 1 + pdoc/test/__init__.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/pdoc/templates/html.mako b/pdoc/templates/html.mako index 7964ec92..8c731827 100644 --- a/pdoc/templates/html.mako +++ b/pdoc/templates/html.mako @@ -411,6 +411,7 @@ % endif % if latex_math: + % endif diff --git a/pdoc/test/__init__.py b/pdoc/test/__init__.py index 4a488c55..4708f05d 100644 --- a/pdoc/test/__init__.py +++ b/pdoc/test/__init__.py @@ -1665,6 +1665,8 @@ def test_latex_math(self): expected = r'''

Inline equation: v_t *\frac{1}{2}* j_i + [a] < 3 .

Block equation: v_t *\frac{1}{2}* j_i + [a] < 3

Block equation: v_t *\frac{1}{2}* j_i + [a] < 3

+

$\mathcal{O}(N)$

+

Escaping \$ should work in math like $X = \$3.25$ once it is implemented.

v_t *\frac{1}{2}* j_i + [a] < 3

''' # noqa: E501 text = inspect.getdoc(self._docmodule.latex_math) html = to_html(text, module=self._module, link=self._link, latex_math=True) From 357b4506fcfe2482bd9c877c52ebea4ca7e4d939 Mon Sep 17 00:00:00 2001 From: Kernc Date: Sat, 22 Jun 2024 01:02:29 +0200 Subject: [PATCH 20/31] REF: Templates: Update CDN resource links --- pdoc/templates/_lunr_search.inc.mako | 4 ++-- pdoc/templates/config.mako | 2 +- pdoc/templates/html.mako | 13 ++++++++----- pdoc/templates/search.mako | 6 +++--- pdoc/test/example_pkg/__init__.py | 4 ++++ 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/pdoc/templates/_lunr_search.inc.mako b/pdoc/templates/_lunr_search.inc.mako index 19dd4323..d7b4f663 100644 --- a/pdoc/templates/_lunr_search.inc.mako +++ b/pdoc/templates/_lunr_search.inc.mako @@ -2,8 +2,8 @@ - - + +