pdoc 15.0.0__py3-none-any.whl → 15.0.2__py3-none-any.whl

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.
pdoc/__init__.py CHANGED
@@ -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.0" # this is read from setup.py
484
+ __version__ = "15.0.2" # this is read from setup.py
485
485
 
486
486
  from pathlib import Path
487
487
  from typing import overload
pdoc/doc.py CHANGED
@@ -445,7 +445,7 @@ class Module(Namespace[types.ModuleType]):
445
445
 
446
446
  mod = _safe_getattr(obj, "__module__", None)
447
447
  qual = _safe_getattr(obj, "__qualname__", None)
448
- if mod and qual and "<locals>" not in qual:
448
+ if mod and isinstance(qual, str) and "<locals>" not in qual:
449
449
  return mod, qual
450
450
  else:
451
451
  # This might be wrong, but it's the best guess we have.
@@ -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 ""
pdoc/doc_ast.py CHANGED
@@ -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
 
pdoc/doc_types.py CHANGED
@@ -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."
pdoc/docstrings.py CHANGED
@@ -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
 
pdoc/extract.py CHANGED
@@ -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,
pdoc/render_helpers.py CHANGED
@@ -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(
@@ -6,7 +6,7 @@
6
6
  }
7
7
  </style>
8
8
  <script type="module" defer>
9
- import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
9
+ import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
10
10
 
11
11
  /* Re-invoke Mermaid when DOM content changes, for example during search. */
12
12
  document.addEventListener("DOMContentLoaded", () => {
pdoc/web.py CHANGED
@@ -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.0
3
+ Version: 15.0.2
4
4
  Summary: API Documentation for Python Projects
5
5
  Author-email: Maximilian Hils <pdoc@maximilianhils.com>
6
6
  License: MIT-0
@@ -1,17 +1,17 @@
1
- pdoc/__init__.py,sha256=xSkeVfRtnTw9sR4YA5zADiCIwKAMfJllvOA1lBYwh_U,21456
1
+ pdoc/__init__.py,sha256=jnoeoAPOPSzWTb12xjX2gUW4SV_TUCS1QS6JdnavNOA,21456
2
2
  pdoc/__main__.py,sha256=DOXWKG7I0M-E9uSs8a--9RGJNI266_2VKZpVQR4paj0,8416
3
3
  pdoc/_compat.py,sha256=iXM2kfvS8oRg30EuA-HYWyVt5iorA5JrJHAhUZvsuu4,1447
4
- pdoc/doc.py,sha256=yu9NcXf35xtd5-_a_a0-ePNrLt_238uvlVvva3k40G4,49848
5
- pdoc/doc_ast.py,sha256=WRkdDTWRNz4pJYBpWUqOGL9yZmcMezCR_uJVJ2ST9NU,11357
4
+ pdoc/doc.py,sha256=866peOsabjNx-rexUlF41K9GYBYHqEaMv_fQUuF8gD4,50099
5
+ pdoc/doc_ast.py,sha256=qoWLta30b2XTlwj9fI15IM0687T0dIp9Vk5bB325O60,11345
6
6
  pdoc/doc_pyi.py,sha256=r2QdI8WeUxY6jQ9O4xmL3m1zPVmIpxEAviqLYt_M5gk,5078
7
- pdoc/doc_types.py,sha256=LQp2AWXNUOF8chQzrPK1RZlGZNnkNswkagMTyXi9ABs,8465
8
- pdoc/docstrings.py,sha256=2s1ao_Ky7pOKgKXmIr7yJfFEIDD_m-5LxoT7yjNmVJs,16384
9
- pdoc/extract.py,sha256=Vb9vVjtJ1u6X2wqn2Z_cTObrunQ722T0Kj12nsqVG20,14379
7
+ pdoc/doc_types.py,sha256=tcZysNBSHk1tAxbr4JSmDARoLkHlxINVW6UYvEtzUvs,8465
8
+ pdoc/docstrings.py,sha256=m3yzwekXlQxY-_wcVEyLzT6A6hJyY-yp95ooyzx90uA,17187
9
+ pdoc/extract.py,sha256=1EtgteSdCF61Z5kr4nR3fL9BXwKoJ9FM92aNCPS48nM,14227
10
10
  pdoc/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  pdoc/render.py,sha256=8vXpDlqzRayJdaH0oU6nA6aApFYeGrtxVLOmCD7EbIU,7081
12
- pdoc/render_helpers.py,sha256=LJcMlaJFhKBNRGiwEbtIFbEqlncgXte97wTfxm5zDk0,20365
12
+ pdoc/render_helpers.py,sha256=Db-uB28WkWird--4Rtu4klJnP6S7p3LJFsaAXiH4fJQ,20395
13
13
  pdoc/search.py,sha256=RGFaRftEQOg1Mw4FEOmJVRY9DhBncBYSFi5r4MSknTM,7248
14
- pdoc/web.py,sha256=eIXR0SpU843D-u_xjrXiQ7vH5Zuda_yWOWV8H5nEaMQ,6853
14
+ pdoc/web.py,sha256=9pRpukVWMEoWbthB1E9Yrg86KoSNmXMeBicno_cVcPQ,6933
15
15
  pdoc/markdown2/LICENSE,sha256=BfcOT5Iu-7wDaKcbIta8wkP-pFncOu4yXeBlMfbeYGI,1116
16
16
  pdoc/markdown2/README.md,sha256=-b2NGwLPzTBnaCGPSqRCzHxSrqArlXxGG5w0c6pOqFk,200
17
17
  pdoc/markdown2/__init__.py,sha256=guvOlezAha8NQBH8TFkEwiP4zFy5tsV2NVYiEOWD2jg,159248
@@ -22,7 +22,7 @@ pdoc/templates/custom.css,sha256=62cn8AmBJiplFJyAKXoZSlVa1s3lNOjkYwDp20pF-ew,106
22
22
  pdoc/templates/layout.css,sha256=xv7AgPtHiazW7S2AtNebRr2BKmOSPmX2wwcejXBRfQ0,4670
23
23
  pdoc/templates/livereload.html.jinja2,sha256=VHbZWN_dBRgBpaRzszvXbbX278-fzbok0JRIOeLep_E,566
24
24
  pdoc/templates/math.html.jinja2,sha256=Jov3pJu5bnouB3jO1Mqoj6aERFlKwiOlnE_0-z4jycE,998
25
- pdoc/templates/mermaid.html.jinja2,sha256=cZSmk1Zmec2HaziyEiz2jgmjzQmrG-ZhQISGrhXa1Qc,625
25
+ pdoc/templates/mermaid.html.jinja2,sha256=GtfEIvKCrYBxFgVR2R17og4Jw2TP0hmQ0nZLH7QtEww,625
26
26
  pdoc/templates/search.html.jinja2,sha256=6mfYR7S8bI7aprnZFCNvusnji5Zj-QpufaCjSMUQ3Bc,7497
27
27
  pdoc/templates/search.js.jinja2,sha256=pRH-19e80c7Nlu9cKckse4RRruRoYjyiDZscZ3nIdbs,1773
28
28
  pdoc/templates/syntax-highlighting.css,sha256=h8SkYVmzf9s4bZiHDPQPytCBuJCeIOK2Kj17V7fmcjM,4545
@@ -47,9 +47,9 @@ pdoc/templates/resources/info-circle-fill.svg,sha256=kO3AMXfWtacpJPzC8Pvihf46OZd
47
47
  pdoc/templates/resources/lightning-fill.svg,sha256=XEyCtbgxeAlwCezdsf7N0NFd5aMjwqyJJDpaFbYYTFA,265
48
48
  pdoc/templates/resources/navtoggle.svg,sha256=WVR0BJIucX0MgwwEawmfX0qYD1i_dSbUhoGnqPef3jw,187
49
49
  pdoc/templates/resources/pdoc-logo.svg,sha256=w5OsMmytDaA2Fr9CobeQQFxBNx4-wFFHtLvkORj0gjk,6989
50
- pdoc-15.0.0.dist-info/LICENSE,sha256=LrhIeJ7gKTDPyOX9YVuZGr9mpmyjpkvqH6LjxvE0szM,898
51
- pdoc-15.0.0.dist-info/METADATA,sha256=rIAkzzfo4_YcfmfNP1cHub7TV5ZiILawKid-TBs0QIc,6775
52
- pdoc-15.0.0.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
53
- pdoc-15.0.0.dist-info/entry_points.txt,sha256=-bK-S1ZvmqCWqi1hGnsl5nayWkzXB1BEs-Cynh5QZaI,43
54
- pdoc-15.0.0.dist-info/top_level.txt,sha256=rg5eIToBHzwTfZZi1E7NVHgie5joQuSuU1rWV0qKS9k,5
55
- pdoc-15.0.0.dist-info/RECORD,,
50
+ pdoc-15.0.2.dist-info/LICENSE,sha256=LrhIeJ7gKTDPyOX9YVuZGr9mpmyjpkvqH6LjxvE0szM,898
51
+ pdoc-15.0.2.dist-info/METADATA,sha256=448hBCXQ-y-3rZfozXBq0dbJZG83T24O-pBdooMTsos,6775
52
+ pdoc-15.0.2.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
53
+ pdoc-15.0.2.dist-info/entry_points.txt,sha256=-bK-S1ZvmqCWqi1hGnsl5nayWkzXB1BEs-Cynh5QZaI,43
54
+ pdoc-15.0.2.dist-info/top_level.txt,sha256=rg5eIToBHzwTfZZi1E7NVHgie5joQuSuU1rWV0qKS9k,5
55
+ pdoc-15.0.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.1.0)
2
+ Generator: setuptools (75.3.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
File without changes