pdoc 15.0.1__tar.gz → 15.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. {pdoc-15.0.1 → pdoc-15.0.3}/CHANGELOG.md +18 -0
  2. {pdoc-15.0.1/pdoc.egg-info → pdoc-15.0.3}/PKG-INFO +1 -1
  3. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/__init__.py +1 -1
  4. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/doc.py +5 -2
  5. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/doc_ast.py +1 -1
  6. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/doc_types.py +1 -1
  7. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/docstrings.py +44 -22
  8. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/extract.py +5 -13
  9. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/render_helpers.py +6 -6
  10. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/content.css +22 -1
  11. pdoc-15.0.3/pdoc/templates/resources/exclamation-octagon-fill.svg +1 -0
  12. pdoc-15.0.3/pdoc/templates/resources/exclamation-square-fill.svg +1 -0
  13. pdoc-15.0.3/pdoc/templates/resources/lightbulb.svg +1 -0
  14. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/web.py +2 -0
  15. {pdoc-15.0.1 → pdoc-15.0.3/pdoc.egg-info}/PKG-INFO +1 -1
  16. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc.egg-info/SOURCES.txt +3 -0
  17. {pdoc-15.0.1 → pdoc-15.0.3}/test/test_docstrings.py +14 -1
  18. {pdoc-15.0.1 → pdoc-15.0.3}/test/test_smoke.py +1 -1
  19. {pdoc-15.0.1 → pdoc-15.0.3}/test/test_web.py +6 -0
  20. {pdoc-15.0.1 → pdoc-15.0.3}/LICENSE +0 -0
  21. {pdoc-15.0.1 → pdoc-15.0.3}/MANIFEST.in +0 -0
  22. {pdoc-15.0.1 → pdoc-15.0.3}/README.md +0 -0
  23. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/__main__.py +0 -0
  24. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/_compat.py +0 -0
  25. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/doc_pyi.py +0 -0
  26. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/markdown2/LICENSE +0 -0
  27. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/markdown2/README.md +0 -0
  28. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/markdown2/__init__.py +0 -0
  29. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/py.typed +0 -0
  30. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/render.py +0 -0
  31. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/search.py +0 -0
  32. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/README.md +0 -0
  33. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/build-search-index.js +0 -0
  34. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/custom.css +0 -0
  35. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/default/error.html.jinja2 +0 -0
  36. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/default/frame.html.jinja2 +0 -0
  37. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/default/index.html.jinja2 +0 -0
  38. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/default/module.html.jinja2 +0 -0
  39. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/deprecated/README.md +0 -0
  40. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/deprecated/bootstrap-reboot.min.css +0 -0
  41. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/deprecated/box-arrow-in-left.svg +0 -0
  42. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/deprecated/elasticlunr.min.js +0 -0
  43. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/deprecated/favicon.svg +0 -0
  44. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/deprecated/navtoggle.svg +0 -0
  45. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/deprecated/pdoc-logo.svg +0 -0
  46. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/deprecated/resources/favicon.svg +0 -0
  47. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/layout.css +0 -0
  48. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/livereload.html.jinja2 +0 -0
  49. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/math.html.jinja2 +0 -0
  50. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/mermaid.html.jinja2 +0 -0
  51. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/resources/bootstrap-reboot.min.css +0 -0
  52. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/resources/box-arrow-in-left.svg +0 -0
  53. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/resources/elasticlunr.min.js +0 -0
  54. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/resources/exclamation-triangle-fill.svg +0 -0
  55. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/resources/info-circle-fill.svg +0 -0
  56. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/resources/lightning-fill.svg +0 -0
  57. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/resources/navtoggle.svg +0 -0
  58. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/resources/pdoc-logo.svg +0 -0
  59. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/search.html.jinja2 +0 -0
  60. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/search.js.jinja2 +0 -0
  61. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/syntax-highlighting.css +0 -0
  62. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc/templates/theme.css +0 -0
  63. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc.egg-info/dependency_links.txt +0 -0
  64. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc.egg-info/entry_points.txt +0 -0
  65. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc.egg-info/requires.txt +0 -0
  66. {pdoc-15.0.1 → pdoc-15.0.3}/pdoc.egg-info/top_level.txt +0 -0
  67. {pdoc-15.0.1 → pdoc-15.0.3}/pyproject.toml +0 -0
  68. {pdoc-15.0.1 → pdoc-15.0.3}/setup.cfg +0 -0
  69. {pdoc-15.0.1 → pdoc-15.0.3}/test/test_doc.py +0 -0
  70. {pdoc-15.0.1 → pdoc-15.0.3}/test/test_doc_ast.py +0 -0
  71. {pdoc-15.0.1 → pdoc-15.0.3}/test/test_doc_pyi.py +0 -0
  72. {pdoc-15.0.1 → pdoc-15.0.3}/test/test_doc_types.py +0 -0
  73. {pdoc-15.0.1 → pdoc-15.0.3}/test/test_extract.py +0 -0
  74. {pdoc-15.0.1 → pdoc-15.0.3}/test/test_main.py +0 -0
  75. {pdoc-15.0.1 → pdoc-15.0.3}/test/test_render_helpers.py +0 -0
  76. {pdoc-15.0.1 → pdoc-15.0.3}/test/test_search.py +0 -0
  77. {pdoc-15.0.1 → pdoc-15.0.3}/test/test_snapshot.py +0 -0
@@ -5,6 +5,24 @@
5
5
  ## Unreleased: pdoc next
6
6
 
7
7
 
8
+ ## 2025-04-21: pdoc 15.0.3
9
+
10
+ - Add missing styles for Github's markdown alerts.
11
+ ([#796](https://github.com/mitmproxy/pdoc/pull/796), @Steve-Tech)
12
+
13
+ ## 2025-04-17: pdoc 15.0.2
14
+
15
+ - Fix a bug where type aliases wouldn't be linked.
16
+ ([#798](https://github.com/mitmproxy/pdoc/pull/798), @mhils)
17
+ - Fix a bug where invalid Numpydoc docstrings would raise an exception.
18
+ ([#789](https://github.com/mitmproxy/pdoc/pull/789), @tobiscode, @mhils)
19
+ - Improve debuggability of docstring processing
20
+ ([#784](https://github.com/mitmproxy/pdoc/pull/784), @tobiscode)
21
+ - Fix handling of URL-escaped module names
22
+ ([#787](https://github.com/mitmproxy/pdoc/pull/787), @iFreilicht)
23
+ - Embed local images referenced in docstrings with an HTML image tag (`<img src="./image.png">`) in addition to Markdown (`![image](./image.png)`)
24
+ ([#785](https://github.com/mitmproxy/pdoc/pull/785), @earshinov)
25
+
8
26
  ## 2024-12-12: pdoc 15.0.1
9
27
 
10
28
  - Update Mermaid.js version.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pdoc
3
- Version: 15.0.1
3
+ Version: 15.0.3
4
4
  Summary: API Documentation for Python Projects
5
5
  Author-email: Maximilian Hils <pdoc@maximilianhils.com>
6
6
  License: MIT-0
@@ -481,7 +481,7 @@ You can find an example in [`examples/library-usage`](https://github.com/mitmpro
481
481
  from __future__ import annotations
482
482
 
483
483
  __docformat__ = "markdown" # explicitly disable rST processing in the examples above.
484
- __version__ = "15.0.1" # this is read from setup.py
484
+ __version__ = "15.0.3" # this is read from setup.py
485
485
 
486
486
  from pathlib import Path
487
487
  from typing import overload
@@ -637,7 +637,7 @@ class Class(Namespace[type]):
637
637
  for attr, unresolved_annotation in dynamic_annotations.items():
638
638
  cls_annotations[attr] = unresolved_annotation
639
639
  cls_fullname = (
640
- _safe_getattr(cls, "__module__", "") + "." + cls.__qualname__
640
+ (_safe_getattr(cls, "__module__", "") or "") + "." + cls.__qualname__
641
641
  ).lstrip(".")
642
642
 
643
643
  new_annotations = {
@@ -1095,7 +1095,7 @@ class Variable(Doc[None]):
1095
1095
  default = f" = {self.default_value_str}"
1096
1096
  else:
1097
1097
  default = ""
1098
- return f'<var {self.qualname.rsplit(".")[-1]}{self.annotation_str}{default}{_docstr(self)}>'
1098
+ return f"<var {self.qualname.rsplit('.')[-1]}{self.annotation_str}{default}{_docstr(self)}>"
1099
1099
 
1100
1100
  @cached_property
1101
1101
  def is_classvar(self) -> bool:
@@ -1169,6 +1169,9 @@ class Variable(Doc[None]):
1169
1169
  """The variable's type annotation as a pretty-printed str."""
1170
1170
  if self.annotation is not empty:
1171
1171
  formatted = formatannotation(self.annotation)
1172
+ # type aliases don't include the module name in their __repr__, so we add it here.
1173
+ if isinstance(self.annotation, TypeAliasType):
1174
+ formatted = f"{self.annotation.__module__}.{formatted}"
1172
1175
  return f": {_remove_collections_abc(formatted)}"
1173
1176
  else:
1174
1177
  return ""
@@ -273,7 +273,7 @@ def _parse(
273
273
  try:
274
274
  return ast.parse(_dedent(source))
275
275
  except Exception as e:
276
- warnings.warn(f"Error parsing source code: {e}\n" f"===\n" f"{source}\n" f"===")
276
+ warnings.warn(f"Error parsing source code: {e}\n===\n{source}\n===")
277
277
  return ast.parse("")
278
278
 
279
279
 
@@ -114,7 +114,7 @@ def safe_eval_type(
114
114
  f"Error parsing type annotation {t} for {fullname}: {e}. "
115
115
  f"You are likely attempting to use Python 3.10 syntax (PEP 604 union types) with an older Python "
116
116
  f"release. `X | Y`-style type annotations are invalid syntax on Python {py_ver}, which is what your "
117
- f"pdoc instance is using. `from future import __annotations__` (PEP 563) postpones evaluation of "
117
+ f"pdoc instance is using. `from __future__ import annotations` (PEP 563) postpones evaluation of "
118
118
  f"annotations, which is why your program won't crash right away. However, pdoc needs to evaluate your "
119
119
  f"type annotations and is unable to do so on Python {py_ver}. To fix this issue, either invoke pdoc "
120
120
  f"from Python 3.10+, or switch to `typing.Union[]` syntax."
@@ -24,6 +24,13 @@ from textwrap import dedent
24
24
  from textwrap import indent
25
25
  import warnings
26
26
 
27
+ AnyException = (SystemExit, GeneratorExit, Exception)
28
+ """BaseException, but excluding KeyboardInterrupt.
29
+
30
+ Modules may raise SystemExit on import (which we want to catch),
31
+ but we don't want to catch a user's KeyboardInterrupt.
32
+ """
33
+
27
34
 
28
35
  @cache
29
36
  def convert(docstring: str, docformat: str, source_file: Path | None) -> str:
@@ -32,39 +39,52 @@ def convert(docstring: str, docformat: str, source_file: Path | None) -> str:
32
39
  """
33
40
  docformat = docformat.lower()
34
41
 
35
- if any(x in docformat for x in ["google", "numpy", "restructuredtext"]):
36
- docstring = rst(docstring, source_file)
42
+ try:
43
+ if any(x in docformat for x in ["google", "numpy", "restructuredtext"]):
44
+ docstring = rst(docstring, source_file)
37
45
 
38
- if "google" in docformat:
39
- docstring = google(docstring)
46
+ if "google" in docformat:
47
+ docstring = google(docstring)
40
48
 
41
- if "numpy" in docformat:
42
- docstring = numpy(docstring)
49
+ if "numpy" in docformat:
50
+ docstring = numpy(docstring)
43
51
 
44
- if source_file is not None and os.environ.get("PDOC_EMBED_IMAGES") != "0":
45
- docstring = embed_images(docstring, source_file)
52
+ if source_file is not None and os.environ.get("PDOC_EMBED_IMAGES") != "0":
53
+ docstring = embed_images(docstring, source_file)
54
+
55
+ except AnyException as e:
56
+ raise RuntimeError(
57
+ 'Docstring processing failed for docstring=\n"""\n'
58
+ + docstring
59
+ + f'\n"""\n{source_file=}\n{docformat=}'
60
+ ) from e
46
61
 
47
62
  return docstring
48
63
 
49
64
 
50
65
  def embed_images(docstring: str, source_file: Path) -> str:
66
+ def local_image_to_data_uri(href: str) -> str:
67
+ image_path = source_file.parent / href
68
+ image_data = image_path.read_bytes()
69
+ image_mime = mimetypes.guess_type(image_path)[0]
70
+ image_data_b64 = base64.b64encode(image_data).decode()
71
+ return f"data:{image_mime};base64,{image_data_b64}"
72
+
51
73
  def embed_local_image(m: re.Match) -> str:
52
- image_path = source_file.parent / m["href"]
53
74
  try:
54
- image_data = image_path.read_bytes()
55
- image_mime = mimetypes.guess_type(image_path)[0]
75
+ href = local_image_to_data_uri(m["href"])
56
76
  except Exception:
57
77
  return m[0]
58
78
  else:
59
- data = base64.b64encode(image_data).decode()
60
- return f"![{m['alt']}](data:{image_mime};base64,{data})"
61
-
62
- return re.sub(
63
- r"!\[\s*(?P<alt>.*?)\s*]\(\s*(?P<href>.+?)\s*\)",
64
- embed_local_image,
65
- docstring,
66
- )
67
- # TODO: Could probably do more here, e.g. support rST or raw HTML replacements.
79
+ return m["before"] + href + m["after"]
80
+
81
+ # TODO: Could probably do more here, e.g. support rST replacements.
82
+ for regex in [
83
+ r"(?P<before>!\[\s*.*?\s*]\(\s*)(?P<href>.+?)(?P<after>\s*\))",
84
+ r"""(?P<before>src=['"])(?P<href>.+?)(?P<after>['"])""",
85
+ ]:
86
+ docstring = re.sub(regex, embed_local_image, docstring)
87
+ return docstring
68
88
 
69
89
 
70
90
  def google(docstring: str) -> str:
@@ -178,13 +198,15 @@ def numpy(docstring: str) -> str:
178
198
  )
179
199
  contents = sections[0]
180
200
  for heading, content in zip(sections[1::2], sections[2::2]):
181
- if content.startswith(" "):
201
+ if content.startswith(" ") and re.search(r"\n(?![ \n])", content):
182
202
  # If the first line of section content is indented, we consider the section to be finished
183
203
  # on the first non-indented line. We take out the rest - the tail - here.
184
204
  content, tail = re.split(r"\n(?![ \n])", content, maxsplit=1)
185
205
  else:
186
206
  tail = ""
187
207
 
208
+ content = dedent(content)
209
+
188
210
  if heading in (
189
211
  "Parameters",
190
212
  "Returns",
@@ -199,7 +221,7 @@ def numpy(docstring: str) -> str:
199
221
  elif heading == "See Also":
200
222
  contents += f"###### {heading}\n{_numpy_seealso(content)}"
201
223
  else:
202
- contents += f"###### {heading}\n{dedent(content)}"
224
+ contents += f"###### {heading}\n{content}"
203
225
  contents += tail
204
226
  return contents
205
227
 
@@ -61,7 +61,7 @@ def walk_specs(specs: Sequence[Path | str]) -> list[str]:
61
61
  modspec = importlib.util.find_spec(modname)
62
62
  if modspec is None:
63
63
  raise ModuleNotFoundError(modname)
64
- except AnyException:
64
+ except pdoc.docstrings.AnyException:
65
65
  warnings.warn(
66
66
  f"Cannot find spec for {modname} (from {spec}):\n{traceback.format_exc()}",
67
67
  stacklevel=2,
@@ -110,7 +110,7 @@ def parse_spec(spec: Path | str) -> str:
110
110
  modspec = importlib.util.find_spec(spec)
111
111
  if modspec is None:
112
112
  raise ModuleNotFoundError
113
- except AnyException:
113
+ except pdoc.docstrings.AnyException:
114
114
  # Module does not exist, use local file.
115
115
  spec = pspec
116
116
  else:
@@ -218,18 +218,10 @@ def load_module(module: str) -> types.ModuleType:
218
218
  Returns the imported module."""
219
219
  try:
220
220
  return importlib.import_module(module)
221
- except AnyException as e:
221
+ except pdoc.docstrings.AnyException as e:
222
222
  raise RuntimeError(f"Error importing {module}") from e
223
223
 
224
224
 
225
- AnyException = (SystemExit, GeneratorExit, Exception)
226
- """BaseException, but excluding KeyboardInterrupt.
227
-
228
- Modules may raise SystemExit on import (which we want to catch),
229
- but we don't want to catch a user's KeyboardInterrupt.
230
- """
231
-
232
-
233
225
  def iter_modules2(module: types.ModuleType) -> dict[str, pkgutil.ModuleInfo]:
234
226
  """
235
227
  Returns all direct child modules of a given module.
@@ -315,7 +307,7 @@ def module_mtime(modulename: str) -> float | None:
315
307
  try:
316
308
  with mock_some_common_side_effects():
317
309
  spec = importlib.util.find_spec(modulename)
318
- except AnyException:
310
+ except pdoc.docstrings.AnyException:
319
311
  pass
320
312
  else:
321
313
  if spec is not None and spec.origin is not None:
@@ -365,7 +357,7 @@ def invalidate_caches(module_name: str) -> None:
365
357
  continue # some funky stuff going on - one example is typing.io, which is a class.
366
358
  with mock_some_common_side_effects():
367
359
  importlib.reload(sys.modules[modname])
368
- except AnyException:
360
+ except pdoc.docstrings.AnyException:
369
361
  warnings.warn(
370
362
  f"Error reloading {modname}:\n{traceback.format_exc()}",
371
363
  stacklevel=2,
@@ -99,7 +99,7 @@ Link pattern used for markdown2's [`link-patterns` extra](https://github.com/tre
99
99
 
100
100
 
101
101
  @cache
102
- def highlight(doc: pdoc.doc.Doc) -> str:
102
+ def highlight(doc: pdoc.doc.Doc) -> Markup:
103
103
  """Highlight the source code of a documentation object using pygments."""
104
104
  if isinstance(doc, str): # pragma: no cover
105
105
  warnings.warn(
@@ -114,7 +114,7 @@ def highlight(doc: pdoc.doc.Doc) -> str:
114
114
  return Markup(pygments.highlight(doc.source, lexer, formatter))
115
115
 
116
116
 
117
- def format_signature(sig: inspect.Signature, colon: bool) -> str:
117
+ def format_signature(sig: inspect.Signature, colon: bool) -> Markup:
118
118
  """Format and highlight a function signature using pygments. Returns HTML."""
119
119
  # First get a list with all params as strings.
120
120
  result = pdoc.doc._PrettySignature._params(sig) # type: ignore
@@ -308,7 +308,7 @@ def module_candidates(identifier: str, current_module: str) -> Iterable[str]:
308
308
  @pass_context
309
309
  def linkify(
310
310
  context: Context, code: str, namespace: str = "", shorten: bool = True
311
- ) -> str:
311
+ ) -> Markup:
312
312
  """
313
313
  Link all identifiers in a block of text. Identifiers referencing unknown modules or modules that
314
314
  are not rendered at the moment will be ignored.
@@ -436,7 +436,7 @@ def linkify(
436
436
 
437
437
 
438
438
  @pass_context
439
- def link(context: Context, spec: tuple[str, str], text: str | None = None) -> str:
439
+ def link(context: Context, spec: tuple[str, str], text: str | None = None) -> Markup:
440
440
  """Create a link for a specific `(modulename, qualname)` tuple."""
441
441
  mod: pdoc.doc.Module = context["module"]
442
442
  modulename, qualname = spec
@@ -469,7 +469,7 @@ def link(context: Context, spec: tuple[str, str], text: str | None = None) -> st
469
469
  return Markup(
470
470
  f'<a href="{relative_link(context["module"].modulename, modulename)}{qualname}">{text or fullname}</a>'
471
471
  )
472
- return text or fullname
472
+ return Markup.escape(text or fullname)
473
473
 
474
474
 
475
475
  def edit_url(
@@ -507,7 +507,7 @@ def root_module_name(all_modules: Mapping[str, pdoc.doc.Module]) -> str | None:
507
507
  return None
508
508
 
509
509
 
510
- def minify_css(css: str) -> str:
510
+ def minify_css(css: str) -> Markup:
511
511
  """Do some very basic CSS minification."""
512
512
  css = re.sub(r"[ ]{4}|\n|(?<=[:{}]) | (?=[{}])", "", css)
513
513
  css = re.sub(
@@ -45,13 +45,27 @@ This makes sure that the pdoc styling doesn't leak to the rest of the page when
45
45
  }
46
46
 
47
47
  /* Admonitions are currently not stylable via theme.css */
48
- .pdoc .alert.note {
48
+ .pdoc .alert.note {
49
49
  color: #084298;
50
50
  background-color: #cfe2ff;
51
51
  border-color: #b6d4fe;
52
52
  background-image: url("data:image/svg+xml,{% filter urlencode %}{% include 'resources/info-circle-fill.svg' %}{% endfilter %}");
53
53
  }
54
54
 
55
+ .pdoc .alert.tip {
56
+ color: #0a3622;
57
+ background-color: #d1e7dd;
58
+ border-color: #a3cfbb;
59
+ background-image: url("data:image/svg+xml,{% filter urlencode %}{% include 'resources/lightbulb.svg' %}{% endfilter %}");
60
+ }
61
+
62
+ .pdoc .alert.important {
63
+ color: #055160;
64
+ background-color: #cff4fc;
65
+ border-color: #9eeaf9;
66
+ background-image: url("data:image/svg+xml,{% filter urlencode %}{% include 'resources/exclamation-square-fill.svg' %}{% endfilter %}");
67
+ }
68
+
55
69
  .pdoc .alert.warning {
56
70
  color: #664d03;
57
71
  background-color: #fff3cd;
@@ -59,6 +73,13 @@ This makes sure that the pdoc styling doesn't leak to the rest of the page when
59
73
  background-image: url("data:image/svg+xml,{% filter urlencode %}{% include 'resources/exclamation-triangle-fill.svg' %}{% endfilter %}");
60
74
  }
61
75
 
76
+ .pdoc .alert.caution {
77
+ color: #842029;
78
+ background-color: #f8d7da;
79
+ border-color: #f5c2c7;
80
+ background-image: url("data:image/svg+xml,{% filter urlencode %}{% include 'resources/exclamation-octagon-fill.svg' %}{% endfilter %}");
81
+ }
82
+
62
83
  .pdoc .alert.danger {
63
84
  color: #842029;
64
85
  background-color: #f8d7da;
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="#842029" viewBox="0 0 16 16"><path d="M11.46.146A.5.5 0 0 0 11.107 0H4.893a.5.5 0 0 0-.353.146L.146 4.54A.5.5 0 0 0 0 4.893v6.214a.5.5 0 0 0 .146.353l4.394 4.394a.5.5 0 0 0 .353.146h6.214a.5.5 0 0 0 .353-.146l4.394-4.394a.5.5 0 0 0 .146-.353V4.893a.5.5 0 0 0-.146-.353zM8 4c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995A.905.905 0 0 1 8 4m.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="#055160" viewBox="0 0 16 16"><path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zm6 4c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995A.905.905 0 0 1 8 4m.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="#0a3622" viewBox="0 0 16 16"><path d="M2 6a6 6 0 1 1 10.174 4.31c-.203.196-.359.4-.453.619l-.762 1.769A.5.5 0 0 1 10.5 13a.5.5 0 0 1 0 1 .5.5 0 0 1 0 1l-.224.447a1 1 0 0 1-.894.553H6.618a1 1 0 0 1-.894-.553L5.5 15a.5.5 0 0 1 0-1 .5.5 0 0 1 0-1 .5.5 0 0 1-.46-.302l-.761-1.77a2 2 0 0 0-.453-.618A5.98 5.98 0 0 1 2 6m6-5a5 5 0 0 0-3.479 8.592c.263.254.514.564.676.941L5.83 12h4.342l.632-1.467c.162-.377.413-.687.676-.941A5 5 0 0 0 8 1"/></svg>
@@ -14,6 +14,7 @@ from functools import cache
14
14
  import http.server
15
15
  import traceback
16
16
  from typing import Mapping
17
+ import urllib.parse
17
18
  import warnings
18
19
  import webbrowser
19
20
 
@@ -60,6 +61,7 @@ class DocHandler(http.server.BaseHTTPRequestHandler):
60
61
  return "Not Found: Please normalize all module separators to '/'."
61
62
  else:
62
63
  module_name = path.lstrip("/").removesuffix(".html").replace("/", ".")
64
+ module_name = urllib.parse.unquote(module_name)
63
65
  if module_name not in self.server.all_modules:
64
66
  self.send_response(404)
65
67
  self.send_header("content-type", "text/html")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pdoc
3
- Version: 15.0.1
3
+ Version: 15.0.3
4
4
  Summary: API Documentation for Python Projects
5
5
  Author-email: Maximilian Hils <pdoc@maximilianhils.com>
6
6
  License: MIT-0
@@ -53,8 +53,11 @@ pdoc/templates/deprecated/resources/favicon.svg
53
53
  pdoc/templates/resources/bootstrap-reboot.min.css
54
54
  pdoc/templates/resources/box-arrow-in-left.svg
55
55
  pdoc/templates/resources/elasticlunr.min.js
56
+ pdoc/templates/resources/exclamation-octagon-fill.svg
57
+ pdoc/templates/resources/exclamation-square-fill.svg
56
58
  pdoc/templates/resources/exclamation-triangle-fill.svg
57
59
  pdoc/templates/resources/info-circle-fill.svg
60
+ pdoc/templates/resources/lightbulb.svg
58
61
  pdoc/templates/resources/lightning-fill.svg
59
62
  pdoc/templates/resources/navtoggle.svg
60
63
  pdoc/templates/resources/pdoc-logo.svg
@@ -37,6 +37,19 @@ def test_rst_extract_options_fuzz(s):
37
37
  assert not s or content or options
38
38
 
39
39
 
40
+ def test_convert_exception(monkeypatch):
41
+ def raise_(*_):
42
+ raise Exception
43
+
44
+ monkeypatch.setattr(docstrings, "rst", raise_)
45
+ with pytest.raises(RuntimeError, match="Docstring processing failed"):
46
+ docstrings.convert(
47
+ "Valid minimal docstring without in- or output.",
48
+ "numpy",
49
+ None,
50
+ )
51
+
52
+
40
53
  def test_rst_extract_options():
41
54
  content = (
42
55
  ":alpha: beta\n"
@@ -49,7 +62,7 @@ def test_rst_extract_options():
49
62
  "alpha": "beta",
50
63
  "charlie": "delta:foxtrot",
51
64
  }
52
- assert content == ("\nrest of content\n" ":option ignored: as follows content\n")
65
+ assert content == ("\nrest of content\n:option ignored: as follows content\n")
53
66
 
54
67
 
55
68
  def test_rst_include_trim_lines():
@@ -22,7 +22,7 @@ def test_smoke(module):
22
22
  try:
23
23
  with pdoc.extract.mock_some_common_side_effects():
24
24
  importlib.import_module(module)
25
- except pdoc.extract.AnyException:
25
+ except pdoc.docstrings.AnyException:
26
26
  pass
27
27
  else:
28
28
  try:
@@ -69,6 +69,12 @@ def test_get_module():
69
69
  )
70
70
 
71
71
 
72
+ def test_get_module_url_escape_sequences():
73
+ assert b"make_dataclass" in handle_request(
74
+ b"GET /%64atac%6Ca%73se%73.html HTTP/1.1\r\n\r\n"
75
+ )
76
+
77
+
72
78
  def test_get_dependency():
73
79
  assert b"a template engine written in pure Python" in handle_request(
74
80
  b"GET /jinja2.html HTTP/1.1\r\n\r\n"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes