hippogriffe 0.1.3__tar.gz → 0.2.1__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.
- {hippogriffe-0.1.3 → hippogriffe-0.2.1}/PKG-INFO +12 -10
- {hippogriffe-0.1.3 → hippogriffe-0.2.1}/README.md +11 -9
- {hippogriffe-0.1.3 → hippogriffe-0.2.1}/hippogriffe/_extension.py +75 -37
- {hippogriffe-0.1.3 → hippogriffe-0.2.1}/hippogriffe/_plugin.py +2 -8
- {hippogriffe-0.1.3 → hippogriffe-0.2.1}/pyproject.toml +1 -1
- {hippogriffe-0.1.3 → hippogriffe-0.2.1}/.gitignore +0 -0
- {hippogriffe-0.1.3 → hippogriffe-0.2.1}/LICENSE +0 -0
- {hippogriffe-0.1.3 → hippogriffe-0.2.1}/hippogriffe/__init__.py +0 -0
- {hippogriffe-0.1.3 → hippogriffe-0.2.1}/hippogriffe/assets/_hippogriffe.css +0 -0
- {hippogriffe-0.1.3 → hippogriffe-0.2.1}/hippogriffe/templates/material/hippogriffe/class.html.jinja +0 -0
- {hippogriffe-0.1.3 → hippogriffe-0.2.1}/hippogriffe/templates/material/hippogriffe/fn.html.jinja +0 -0
- {hippogriffe-0.1.3 → hippogriffe-0.2.1}/hippogriffe/templates/material/hippogriffe/hippogriffe.jinja +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: hippogriffe
|
3
|
-
Version: 0.1
|
3
|
+
Version: 0.2.1
|
4
4
|
Summary: Tweaks for `mkdocstrings[python]`
|
5
5
|
Project-URL: repository, https://github.com/patrick-kidger/hippogriffe
|
6
6
|
Author-email: Patrick Kidger <contact@kidger.site>
|
@@ -265,9 +265,9 @@ plugins:
|
|
265
265
|
- hippogriffe:
|
266
266
|
show_bases: true/false
|
267
267
|
show_source_links: all/toplevel/none
|
268
|
-
|
269
|
-
- foo
|
270
|
-
- bar
|
268
|
+
extra_public_objects:
|
269
|
+
- foo.SomeClass
|
270
|
+
- bar.subpackage.some_function
|
271
271
|
```
|
272
272
|
|
273
273
|
**show_bases:**
|
@@ -278,19 +278,21 @@ If `false` then base classes will not be displayed alongside a class. Defaults t
|
|
278
278
|
|
279
279
|
Sets which objects will have links to their location in the repository (as configured via the usual MkDocs `repo_url`). If `all` then all objects will have links. If `toplevel` then just `::: somelib.value` will have links, but their members will not. If `none` then no links will be added. Defaults to `toplevel`.
|
280
280
|
|
281
|
-
**
|
281
|
+
**extra_public_objects:**
|
282
282
|
|
283
|
-
|
283
|
+
Pretty-formatting of type annotations is done strictly: every annotation must be part of the known public API, else an error will be raised. The public API is defined as the combination of:
|
284
284
|
|
285
285
|
- Everything you document using `::: yourlib.Foo`, and all of their members.
|
286
286
|
- Anything from the standard library.
|
287
|
-
- All objects belonging to any of `
|
287
|
+
- All objects belonging to any of `extra_public_objects`.
|
288
288
|
|
289
289
|
For example,
|
290
290
|
```yml
|
291
291
|
plugins:
|
292
292
|
- hippogriffe:
|
293
|
-
|
294
|
-
- jax
|
295
|
-
- torch
|
293
|
+
extra_public_objects:
|
294
|
+
- jax.Array
|
295
|
+
- torch.Tensor
|
296
296
|
```
|
297
|
+
|
298
|
+
List each object under whatever public path `somelib.Foo` that you would like it to be displayed under (and from which it must be accessible), not whichever private path `somelib._internal.foo.Foo` it is defined at.
|
@@ -43,9 +43,9 @@ plugins:
|
|
43
43
|
- hippogriffe:
|
44
44
|
show_bases: true/false
|
45
45
|
show_source_links: all/toplevel/none
|
46
|
-
|
47
|
-
- foo
|
48
|
-
- bar
|
46
|
+
extra_public_objects:
|
47
|
+
- foo.SomeClass
|
48
|
+
- bar.subpackage.some_function
|
49
49
|
```
|
50
50
|
|
51
51
|
**show_bases:**
|
@@ -56,19 +56,21 @@ If `false` then base classes will not be displayed alongside a class. Defaults t
|
|
56
56
|
|
57
57
|
Sets which objects will have links to their location in the repository (as configured via the usual MkDocs `repo_url`). If `all` then all objects will have links. If `toplevel` then just `::: somelib.value` will have links, but their members will not. If `none` then no links will be added. Defaults to `toplevel`.
|
58
58
|
|
59
|
-
**
|
59
|
+
**extra_public_objects:**
|
60
60
|
|
61
|
-
|
61
|
+
Pretty-formatting of type annotations is done strictly: every annotation must be part of the known public API, else an error will be raised. The public API is defined as the combination of:
|
62
62
|
|
63
63
|
- Everything you document using `::: yourlib.Foo`, and all of their members.
|
64
64
|
- Anything from the standard library.
|
65
|
-
- All objects belonging to any of `
|
65
|
+
- All objects belonging to any of `extra_public_objects`.
|
66
66
|
|
67
67
|
For example,
|
68
68
|
```yml
|
69
69
|
plugins:
|
70
70
|
- hippogriffe:
|
71
|
-
|
72
|
-
- jax
|
73
|
-
- torch
|
71
|
+
extra_public_objects:
|
72
|
+
- jax.Array
|
73
|
+
- torch.Tensor
|
74
74
|
```
|
75
|
+
|
76
|
+
List each object under whatever public path `somelib.Foo` that you would like it to be displayed under (and from which it must be accessible), not whichever private path `somelib._internal.foo.Foo` it is defined at.
|
@@ -2,6 +2,7 @@ import ast
|
|
2
2
|
import builtins
|
3
3
|
import contextlib
|
4
4
|
import functools as ft
|
5
|
+
import importlib
|
5
6
|
import inspect
|
6
7
|
import pathlib
|
7
8
|
import re
|
@@ -33,23 +34,38 @@ class _PublicApi:
|
|
33
34
|
pkg: griffe.Module,
|
34
35
|
top_level_public_api: set[str],
|
35
36
|
builtin_modules: list[str],
|
36
|
-
|
37
|
-
extra_public_modules: list[str],
|
37
|
+
extra_public_objects: list[str],
|
38
38
|
):
|
39
39
|
self._objects: set[griffe.Object] = set()
|
40
40
|
self._toplevel_objects: set[griffe.Object] = set()
|
41
41
|
self._data: dict[str, list[str]] = {}
|
42
42
|
self._builtin_modules = builtin_modules
|
43
|
-
|
43
|
+
for object_path in extra_public_objects:
|
44
|
+
object_pieces = object_path.split(".")
|
45
|
+
for i in reversed(range(1, len(object_pieces))):
|
46
|
+
module_name = "".join(object_pieces[:i])
|
47
|
+
object_name = object_pieces[i:]
|
48
|
+
try:
|
49
|
+
object = importlib.import_module(module_name)
|
50
|
+
except Exception:
|
51
|
+
continue
|
52
|
+
for object_piece in object_name:
|
53
|
+
object = getattr(object, object_piece)
|
54
|
+
private_path = f"{object.__module__}.{object.__qualname__}"
|
55
|
+
try:
|
56
|
+
paths = self._data[private_path]
|
57
|
+
except KeyError:
|
58
|
+
paths = self._data[private_path] = []
|
59
|
+
paths.append(object_path)
|
44
60
|
# Don't infinite loop on cycles. We only store Objects, and not Aliases, as in
|
45
61
|
# cycles then the aliases with be distinct: `X.Y.X.Y` is not `X.Y`, though the
|
46
62
|
# underlying object is the same.
|
47
63
|
|
48
|
-
agenda: list[tuple[griffe.Object, bool]] = [(pkg, False)]
|
64
|
+
agenda: list[tuple[griffe.Object, str, bool]] = [(pkg, pkg.path, False)]
|
49
65
|
seen: set[griffe.Object] = {pkg}
|
50
66
|
while len(agenda) > 0:
|
51
|
-
item, force_public = agenda.pop()
|
52
|
-
toplevel_public =
|
67
|
+
item, public_path, force_public = agenda.pop()
|
68
|
+
toplevel_public = public_path in top_level_public_api
|
53
69
|
if force_public or toplevel_public:
|
54
70
|
# If we're in the public API, then we consider all of our children to be
|
55
71
|
# in it as well... (this saves us from having to parse out `filters` and
|
@@ -58,7 +74,7 @@ class _PublicApi:
|
|
58
74
|
paths = self._data[item.path]
|
59
75
|
except KeyError:
|
60
76
|
paths = self._data[item.path] = []
|
61
|
-
paths.append(
|
77
|
+
paths.append(public_path)
|
62
78
|
self._objects.add(item)
|
63
79
|
if toplevel_public:
|
64
80
|
self._toplevel_objects.add(item)
|
@@ -70,13 +86,13 @@ class _PublicApi:
|
|
70
86
|
for member in item.all_members.values():
|
71
87
|
# Skip private elements
|
72
88
|
if member.name.startswith("_") and not (
|
73
|
-
member.name.startswith("__") and
|
89
|
+
member.name.startswith("__") and member.name.endswith("__")
|
74
90
|
):
|
75
91
|
continue
|
76
92
|
if isinstance(member, griffe.Alias):
|
77
93
|
try:
|
78
94
|
final_member = member.final_target
|
79
|
-
except griffe.AliasResolutionError:
|
95
|
+
except (griffe.AliasResolutionError, griffe.CyclicAliasError):
|
80
96
|
continue
|
81
97
|
if member.name != final_member.name:
|
82
98
|
# Renaming during import counts as private.
|
@@ -87,7 +103,7 @@ class _PublicApi:
|
|
87
103
|
final_member = member
|
88
104
|
if final_member in seen:
|
89
105
|
continue
|
90
|
-
agenda.append((final_member, sub_force_public))
|
106
|
+
agenda.append((final_member, member.path, sub_force_public))
|
91
107
|
seen.add(final_member)
|
92
108
|
|
93
109
|
def toplevel(self) -> Iterable[griffe.Object]:
|
@@ -103,16 +119,17 @@ class _PublicApi:
|
|
103
119
|
for m in self._builtin_modules:
|
104
120
|
if key.startswith(m + "."):
|
105
121
|
return key.removeprefix(m + "."), False
|
106
|
-
for m in
|
122
|
+
for m in sys.stdlib_module_names:
|
107
123
|
if key.startswith(m + "."):
|
108
124
|
return key, False
|
109
|
-
#
|
125
|
+
# Note that this message must not have any newlines in it, to display
|
126
|
+
# correctly.
|
110
127
|
raise _NotInPublicApiException(
|
111
|
-
f"Tried and failed to find {key} in the public API. Commons reasons "
|
112
|
-
"for this error are
|
113
|
-
"
|
114
|
-
"
|
115
|
-
"
|
128
|
+
f"Tried and failed to find `{key}` in the public API. Commons reasons "
|
129
|
+
"for this error are (1) if it is from outside this package, then this "
|
130
|
+
"object is not listed (under whatever public path it should be "
|
131
|
+
"displayed as) in `hippogriffe.extra_public_objects`; (2) if it is "
|
132
|
+
"from inside this package, then it may have been written "
|
116
133
|
"`::: somelib.Foo:` with a trailing colon, when just `:::somelib.Foo` "
|
117
134
|
"is correct."
|
118
135
|
) from e
|
@@ -211,20 +228,17 @@ def _resolved_bases(cls: griffe.Class) -> list[str | griffe.Object]:
|
|
211
228
|
return resolved_bases
|
212
229
|
|
213
230
|
|
214
|
-
def _collect_bases(
|
215
|
-
cls: griffe.Class, public_api: _PublicApi, public_modules: set[str]
|
216
|
-
) -> dict[str, bool]:
|
231
|
+
def _collect_bases(cls: griffe.Class, public_api: _PublicApi) -> dict[str, bool]:
|
217
232
|
bases: dict[str, bool] = {}
|
218
233
|
for base in _resolved_bases(cls):
|
219
234
|
if isinstance(base, str):
|
220
235
|
# builtins case above
|
221
|
-
|
222
|
-
bases[base] = False
|
236
|
+
bases[base] = False
|
223
237
|
elif isinstance(base, griffe.Class):
|
224
238
|
try:
|
225
239
|
base, autoref = public_api[base.path]
|
226
240
|
except _NotInPublicApiException:
|
227
|
-
bases.update(_collect_bases(base, public_api
|
241
|
+
bases.update(_collect_bases(base, public_api))
|
228
242
|
else:
|
229
243
|
bases[base] = autoref
|
230
244
|
return bases
|
@@ -254,11 +268,23 @@ def _get_repo_url(repo_url: None | str) -> tuple[pathlib.Path, str]:
|
|
254
268
|
else:
|
255
269
|
toplevel = pathlib.Path(git_toplevel.stdout.decode().strip())
|
256
270
|
commit_hash = git_head.stdout.decode().strip()
|
257
|
-
|
258
|
-
|
271
|
+
if "://" in repo_url:
|
272
|
+
protocol, repo_url = repo_url.split("://", 1)
|
273
|
+
protocol = f"{protocol}://"
|
274
|
+
else:
|
275
|
+
protocol = ""
|
276
|
+
supported_site = False
|
277
|
+
if repo_url.startswith("github.com"):
|
278
|
+
supported_site = True
|
279
|
+
fragment = "L{start}-L{end}"
|
280
|
+
elif repo_url.startswith("gitlab.com"):
|
281
|
+
supported_site = True
|
282
|
+
fragment = "L{start}-{end}"
|
283
|
+
if supported_site:
|
284
|
+
# Expect url in the form `https://github.com/org/repo`, strip any trailing paths
|
285
|
+
repo_url = "/".join(repo_url.split("/")[:3])
|
259
286
|
repo_url = (
|
260
|
-
f"{repo_url
|
261
|
-
"#L{start}-{end}"
|
287
|
+
f"{protocol}{repo_url}/blob/{commit_hash}/{{path}}#{fragment}"
|
262
288
|
)
|
263
289
|
else:
|
264
290
|
# We need to format the `repo_url` to what the repo expects, so we have to
|
@@ -324,8 +350,7 @@ class HippogriffeExtension(griffe.Extension):
|
|
324
350
|
pkg,
|
325
351
|
top_level_public_api=self.top_level_public_api,
|
326
352
|
builtin_modules=self.config.builtin_modules,
|
327
|
-
|
328
|
-
extra_public_modules=self.config.extra_public_modules,
|
353
|
+
extra_public_objects=self.config.extra_public_objects,
|
329
354
|
)
|
330
355
|
|
331
356
|
def use_public_name(context: None | dict, obj: Any) -> None | wl.AbstractDoc:
|
@@ -342,21 +367,34 @@ class HippogriffeExtension(griffe.Extension):
|
|
342
367
|
new_path, _ = public_api[f"{obj.__module__}.{obj.__qualname__}"]
|
343
368
|
return wl.TextDoc(new_path)
|
344
369
|
|
345
|
-
public_modules = set(self.config.extra_public_modules) | set(
|
346
|
-
self.config.stdlib_modules
|
347
|
-
)
|
348
370
|
for obj in public_api:
|
349
371
|
if obj.is_function:
|
350
372
|
assert type(obj) is griffe.Function
|
351
|
-
|
352
|
-
|
373
|
+
try:
|
374
|
+
_pretty_fn(obj, use_public_name)
|
375
|
+
except _NotInPublicApiException as e:
|
376
|
+
# Defer error until later -- right now our `public_api` is an
|
377
|
+
# overestimation of the 'true' public API as for a public class
|
378
|
+
# `Foo` then we actually include all of its attributes in the public
|
379
|
+
# API here, even if those aren't documented.
|
380
|
+
# It's fairly common to have nonpublic annotations in nonpublic
|
381
|
+
# methods, and we shouldn't die on those now -- if the method is
|
382
|
+
# never documented then we don't need to worry. Putting this here is
|
383
|
+
# totally sneaky, it's letting jinja think this is a template and
|
384
|
+
# having it raise a TemplateNotFound error when it tries to format
|
385
|
+
# this object.
|
386
|
+
obj.extra["mkdocstrings"]["template"] = (
|
387
|
+
f"{e} This arose whilst pretty-printing `{obj.path}`. You may "
|
388
|
+
"ignore the rest of this error message, which comes from jinja."
|
389
|
+
" "
|
390
|
+
)
|
391
|
+
else:
|
392
|
+
obj.extra["mkdocstrings"]["template"] = "hippogriffe/fn.html.jinja"
|
353
393
|
elif obj.is_class:
|
354
394
|
assert type(obj) is griffe.Class
|
355
395
|
obj.extra["mkdocstrings"]["template"] = "hippogriffe/class.html.jinja"
|
356
396
|
if self.config.show_bases:
|
357
|
-
public_bases = list(
|
358
|
-
_collect_bases(obj, public_api, public_modules).items()
|
359
|
-
)
|
397
|
+
public_bases = list(_collect_bases(obj, public_api).items())
|
360
398
|
obj.extra["hippogriffe"]["public_bases"] = public_bases
|
361
399
|
|
362
400
|
if self.config.show_source_links == "none":
|
@@ -1,6 +1,5 @@
|
|
1
1
|
import pathlib
|
2
2
|
import re
|
3
|
-
import sys
|
4
3
|
import typing
|
5
4
|
|
6
5
|
from mkdocs.config import Config
|
@@ -25,13 +24,8 @@ class PluginConfig(Config):
|
|
25
24
|
show_source_links = Choice(["all", "toplevel", "none"], default="toplevel")
|
26
25
|
"""Whether to include [source] links to the repo."""
|
27
26
|
|
28
|
-
|
29
|
-
"""Any third-party
|
30
|
-
|
31
|
-
stdlib_modules = Type(list, default=list(sys.stdlib_module_names))
|
32
|
-
"""A list of stdlib modules. This is concatenated with `extra_public_modules` to
|
33
|
-
form the list of external modules that are allowed in the public API
|
34
|
-
"""
|
27
|
+
extra_public_objects = Type(list, default=[])
|
28
|
+
"""Any third-party objects which are allowed in the public API."""
|
35
29
|
|
36
30
|
builtin_modules = Type(
|
37
31
|
list, default=["builtins", "collections.abc", "typing", "typing_extensions"]
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{hippogriffe-0.1.3 → hippogriffe-0.2.1}/hippogriffe/templates/material/hippogriffe/class.html.jinja
RENAMED
File without changes
|
{hippogriffe-0.1.3 → hippogriffe-0.2.1}/hippogriffe/templates/material/hippogriffe/fn.html.jinja
RENAMED
File without changes
|
{hippogriffe-0.1.3 → hippogriffe-0.2.1}/hippogriffe/templates/material/hippogriffe/hippogriffe.jinja
RENAMED
File without changes
|