hippogriffe 0.1.2__py3-none-any.whl → 0.2.0__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.
hippogriffe/_extension.py CHANGED
@@ -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,60 +34,77 @@ class _PublicApi:
33
34
  pkg: griffe.Module,
34
35
  top_level_public_api: set[str],
35
36
  builtin_modules: list[str],
36
- stdlib_modules: list[str],
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
- self._public_modules = stdlib_modules + extra_public_modules
44
- seen: set[griffe.Alias | griffe.Object] = set() # Don't infinite loop on cycles
45
-
46
- agenda: list[tuple[griffe.Alias | griffe.Object, bool]] = [(pkg, False)]
47
- while len(agenda) > 0:
48
- item, force_public = agenda.pop()
49
- seen.add(item)
50
- # Skip private elements
51
- if item.name.startswith("_") and not (
52
- item.name.startswith("__") and item.name.endswith("__")
53
- ):
54
- continue
55
- if isinstance(item, griffe.Alias):
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:]
56
48
  try:
57
- final_item = item.final_target
58
- except griffe.AliasResolutionError:
59
- continue
60
- if item.name != final_item.name:
61
- # Renaming during import counts as private.
62
- # (In particular this happens for backward compatibility, e.g.
63
- # `equinox.nn.inference_mode` and `equinox.tree_inference`.)
49
+ object = importlib.import_module(module_name)
50
+ except Exception:
64
51
  continue
65
- else:
66
- final_item = item
67
-
68
- toplevel_public = item.path in top_level_public_api
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)
60
+ # Don't infinite loop on cycles. We only store Objects, and not Aliases, as in
61
+ # cycles then the aliases with be distinct: `X.Y.X.Y` is not `X.Y`, though the
62
+ # underlying object is the same.
63
+
64
+ agenda: list[tuple[griffe.Object, str, bool]] = [(pkg, pkg.path, False)]
65
+ seen: set[griffe.Object] = {pkg}
66
+ while len(agenda) > 0:
67
+ item, public_path, force_public = agenda.pop()
68
+ toplevel_public = public_path in top_level_public_api
69
69
  if force_public or toplevel_public:
70
70
  # If we're in the public API, then we consider all of our children to be
71
71
  # in it as well... (this saves us from having to parse out `filters` and
72
72
  # `members` from our documentation)
73
- agenda.extend(
74
- (x, True) for x in item.all_members.values() if x not in seen
75
- )
76
73
  try:
77
- paths = self._data[final_item.path]
74
+ paths = self._data[item.path]
78
75
  except KeyError:
79
- paths = self._data[final_item.path] = []
80
- paths.append(item.path)
81
- self._objects.add(final_item)
76
+ paths = self._data[item.path] = []
77
+ paths.append(public_path)
78
+ self._objects.add(item)
82
79
  if toplevel_public:
83
- self._toplevel_objects.add(final_item)
80
+ self._toplevel_objects.add(item)
81
+ sub_force_public = True
84
82
  else:
85
83
  # ...if we're not in the public API then check our members -- some of
86
84
  # them might be in the public API.
87
- agenda.extend(
88
- (x, False) for x in item.all_members.values() if x not in seen
89
- )
85
+ sub_force_public = False
86
+ for member in item.all_members.values():
87
+ # Skip private elements
88
+ if member.name.startswith("_") and not (
89
+ member.name.startswith("__") and member.name.endswith("__")
90
+ ):
91
+ continue
92
+ if isinstance(member, griffe.Alias):
93
+ try:
94
+ final_member = member.final_target
95
+ except griffe.AliasResolutionError:
96
+ continue
97
+ if member.name != final_member.name:
98
+ # Renaming during import counts as private.
99
+ # (In particular this happens for backward compatibility, e.g.
100
+ # `equinox.nn.inference_mode` and `equinox.tree_inference`.)
101
+ continue
102
+ else:
103
+ final_member = member
104
+ if final_member in seen:
105
+ continue
106
+ agenda.append((final_member, member.path, sub_force_public))
107
+ seen.add(final_member)
90
108
 
91
109
  def toplevel(self) -> Iterable[griffe.Object]:
92
110
  return self._toplevel_objects
@@ -101,16 +119,17 @@ class _PublicApi:
101
119
  for m in self._builtin_modules:
102
120
  if key.startswith(m + "."):
103
121
  return key.removeprefix(m + "."), False
104
- for m in self._public_modules:
122
+ for m in sys.stdlib_module_names:
105
123
  if key.startswith(m + "."):
106
124
  return key, False
107
- # Not using `KeyError` because that displays its message with `repr`.
125
+ # Note that this message must not have any newlines in it, to display
126
+ # correctly.
108
127
  raise _NotInPublicApiException(
109
- f"Tried and failed to find {key} in the public API. Commons reasons "
110
- "for this error are:\n"
111
- "- If it is from outside this package, then that package is not listed "
112
- "under the `hippogriffe.extra_public_modules`\n"
113
- "- If it is from inside this package, then it may have been written "
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 "
114
133
  "`::: somelib.Foo:` with a trailing colon, when just `:::somelib.Foo` "
115
134
  "is correct."
116
135
  ) from e
@@ -209,20 +228,17 @@ def _resolved_bases(cls: griffe.Class) -> list[str | griffe.Object]:
209
228
  return resolved_bases
210
229
 
211
230
 
212
- def _collect_bases(
213
- cls: griffe.Class, public_api: _PublicApi, public_modules: set[str]
214
- ) -> dict[str, bool]:
231
+ def _collect_bases(cls: griffe.Class, public_api: _PublicApi) -> dict[str, bool]:
215
232
  bases: dict[str, bool] = {}
216
233
  for base in _resolved_bases(cls):
217
234
  if isinstance(base, str):
218
235
  # builtins case above
219
- if "builtins" in public_modules:
220
- bases[base] = False
236
+ bases[base] = False
221
237
  elif isinstance(base, griffe.Class):
222
238
  try:
223
239
  base, autoref = public_api[base.path]
224
240
  except _NotInPublicApiException:
225
- bases.update(_collect_bases(base, public_api, public_modules))
241
+ bases.update(_collect_bases(base, public_api))
226
242
  else:
227
243
  bases[base] = autoref
228
244
  return bases
@@ -322,8 +338,7 @@ class HippogriffeExtension(griffe.Extension):
322
338
  pkg,
323
339
  top_level_public_api=self.top_level_public_api,
324
340
  builtin_modules=self.config.builtin_modules,
325
- stdlib_modules=self.config.stdlib_modules,
326
- extra_public_modules=self.config.extra_public_modules,
341
+ extra_public_objects=self.config.extra_public_objects,
327
342
  )
328
343
 
329
344
  def use_public_name(context: None | dict, obj: Any) -> None | wl.AbstractDoc:
@@ -340,21 +355,34 @@ class HippogriffeExtension(griffe.Extension):
340
355
  new_path, _ = public_api[f"{obj.__module__}.{obj.__qualname__}"]
341
356
  return wl.TextDoc(new_path)
342
357
 
343
- public_modules = set(self.config.extra_public_modules) | set(
344
- self.config.stdlib_modules
345
- )
346
358
  for obj in public_api:
347
359
  if obj.is_function:
348
360
  assert type(obj) is griffe.Function
349
- obj.extra["mkdocstrings"]["template"] = "hippogriffe/fn.html.jinja"
350
- _pretty_fn(obj, use_public_name)
361
+ try:
362
+ _pretty_fn(obj, use_public_name)
363
+ except _NotInPublicApiException as e:
364
+ # Defer error until later -- right now our `public_api` is an
365
+ # overestimation of the 'true' public API as for a public class
366
+ # `Foo` then we actually include all of its attributes in the public
367
+ # API here, even if those aren't documented.
368
+ # It's fairly common to have nonpublic annotations in nonpublic
369
+ # methods, and we shouldn't die on those now -- if the method is
370
+ # never documented then we don't need to worry. Putting this here is
371
+ # totally sneaky, it's letting jinja think this is a template and
372
+ # having it raise a TemplateNotFound error when it tries to format
373
+ # this object.
374
+ obj.extra["mkdocstrings"]["template"] = (
375
+ f"{e} This arose whilst pretty-printing `{obj.path}`. You may "
376
+ "ignore the rest of this error message, which comes from jinja."
377
+ " "
378
+ )
379
+ else:
380
+ obj.extra["mkdocstrings"]["template"] = "hippogriffe/fn.html.jinja"
351
381
  elif obj.is_class:
352
382
  assert type(obj) is griffe.Class
353
383
  obj.extra["mkdocstrings"]["template"] = "hippogriffe/class.html.jinja"
354
384
  if self.config.show_bases:
355
- public_bases = list(
356
- _collect_bases(obj, public_api, public_modules).items()
357
- )
385
+ public_bases = list(_collect_bases(obj, public_api).items())
358
386
  obj.extra["hippogriffe"]["public_bases"] = public_bases
359
387
 
360
388
  if self.config.show_source_links == "none":
hippogriffe/_plugin.py CHANGED
@@ -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
- extra_public_modules = Type(list, default=[])
29
- """Any third-party modules whose objects are allowed in the public API."""
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"]
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hippogriffe
3
- Version: 0.1.2
4
- Summary: A simple ipynb->md converter for MkDocs
3
+ Version: 0.2.0
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>
7
7
  License: Apache License
@@ -265,9 +265,9 @@ plugins:
265
265
  - hippogriffe:
266
266
  show_bases: true/false
267
267
  show_source_links: all/toplevel/none
268
- extra_public_modules:
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
- **extra_public_modules:**
281
+ **extra_public_objects:**
282
282
 
283
- A list of module names whose elements should be treated as part of the public API. 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:
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 `extra_public_modules`.
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
- extra_public_modules:
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 accessilbe), not whatever private path `somelib._internal.foo.Foo` it is defined at.
@@ -1,12 +1,12 @@
1
1
  hippogriffe/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- hippogriffe/_extension.py,sha256=zg3Q5h5ziLOX46BXYOpwrXQ7EFGJP7UwKXjKMh7GYjY,14014
3
- hippogriffe/_plugin.py,sha256=H0oPka_7URED9wOVYAWUNTWTJXFmH6q7g2W2G5hy64c,4564
2
+ hippogriffe/_extension.py,sha256=5c5Hb4qW4v3UjGn2x2MwXB_8CI6PfLUOQ0kO0soflA0,15992
3
+ hippogriffe/_plugin.py,sha256=9omje-ROpvo24YYSIcul8XjKwuzFhV7rE9a1f3XDUws,4307
4
4
  hippogriffe/assets/_hippogriffe.css,sha256=BgyZLCaOaZNgQErdUb01vpNso4ilUMmoZ5d_OeVpfRE,147
5
5
  hippogriffe/templates/material/hippogriffe/class.html.jinja,sha256=5i624gIuZfnf5toH3jWclzg1qlekqIZ2ODbpzeZcULw,954
6
6
  hippogriffe/templates/material/hippogriffe/fn.html.jinja,sha256=vxrWOPkPkGD3Po9hbcYlyEjsJKhJmOHic4G5t2J0djc,196
7
7
  hippogriffe/templates/material/hippogriffe/hippogriffe.jinja,sha256=KxTBKY0XqiFzqTT1iqFxhN2GhmtwLT4GWc8S86zyjOc,583
8
- hippogriffe-0.1.2.dist-info/METADATA,sha256=IiWbdutZDQd0mrtyQyES29rTdHJEyrZmK7I0jQL0UJ0,16274
9
- hippogriffe-0.1.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
- hippogriffe-0.1.2.dist-info/entry_points.txt,sha256=Et3dFNWG-biZ7XCvPI9na-buFT_hht1YT0p0JDNEQQM,158
11
- hippogriffe-0.1.2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
12
- hippogriffe-0.1.2.dist-info/RECORD,,
8
+ hippogriffe-0.2.0.dist-info/METADATA,sha256=Ud_6bXxxqtLRQltCwOPUzH2MfpzAHDcNCeWMfOrpTu8,16449
9
+ hippogriffe-0.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
+ hippogriffe-0.2.0.dist-info/entry_points.txt,sha256=Et3dFNWG-biZ7XCvPI9na-buFT_hht1YT0p0JDNEQQM,158
11
+ hippogriffe-0.2.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
12
+ hippogriffe-0.2.0.dist-info/RECORD,,