Skip to content

Commit 8e3d4f9

Browse files
authored
Merge pull request #233 from machow/feat-option-defaults
Feat option defaults
2 parents bd352e3 + 5a0d33d commit 8e3d4f9

File tree

6 files changed

+223
-87
lines changed

6 files changed

+223
-87
lines changed

docs/_quarto.yml

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -93,19 +93,16 @@ quartodoc:
9393
- title: Docstring Renderers
9494
desc: |
9595
Renderers convert parsed docstrings into a target format, like markdown.
96+
options:
97+
dynamic: true
9698
contents:
9799
- name: MdRenderer
98100
children: linked
99-
- name: MdRenderer.render
100-
dynamic: true
101-
- name: MdRenderer.render_annotation
102-
dynamic: true
103-
- name: MdRenderer.render_header
104-
dynamic: true
105-
- name: MdRenderer.signature
106-
dynamic: true
107-
- name: MdRenderer.summarize
108-
dynamic: true
101+
- MdRenderer.render
102+
- MdRenderer.render_annotation
103+
- MdRenderer.render_header
104+
- MdRenderer.signature
105+
- MdRenderer.summarize
109106

110107
- title: API Builders
111108
desc: |

docs/get-started/basic-content.qmd

Lines changed: 142 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,148 @@ quartodoc:
9999
children: separate
100100
```
101101

102+
103+
## Setting default options
104+
105+
The section above showed how you can set options like `members:` and `children:` on content.
106+
When specifying API documentation, you may need to specify the same options across multiple pieces of content.
107+
108+
Use the `options:` field in a section to specify options to apply across all content in that section.
109+
110+
For example, the config blocks below show how to document multiple classes without their members.
111+
112+
:::::: {.columns}
113+
::: {.column}
114+
115+
**Manual**
116+
117+
```yaml
118+
quartodoc:
119+
package: quartodoc
120+
sections:
121+
- title: "Some section"
122+
123+
# options set manually ---
124+
contents:
125+
- name: MdRenderer
126+
members: []
127+
- name: Builder
128+
members: []
129+
```
130+
131+
:::
132+
::: {.column}
133+
134+
**With options**
135+
136+
```yaml
137+
quartodoc:
138+
package: quartodoc
139+
sections:
140+
- title: "Some section"
141+
142+
# default options ---
143+
options:
144+
members: []
145+
146+
contents:
147+
- MdRenderer
148+
- Builder
149+
```
150+
151+
:::
152+
::::::
153+
154+
Note that the **with options** block sets `members: []` inside `options`.
155+
This sets `members: []` as the default for each piece of content.
156+
157+
::: {.callout-tip}
158+
Options can be given a name, and re-used in multiple sections:
159+
160+
```yaml
161+
- title: Some section
162+
options: &no-members
163+
members: []
164+
content:
165+
- ThingA
166+
- ThingB
167+
- title: Another section
168+
options: *no-members
169+
content:
170+
- ThingC
171+
```
172+
173+
The code above uses `&no-members` to name the options in the first section "no-members",
174+
then `*no-members` to reference it in the second section.
175+
The `&` and `*` are called an anchor and alias, respectively, in YAML.
176+
:::
177+
178+
## Specifying package path
179+
180+
Different levels of configuration let you set the `package` option.
181+
This controls the package path that quartodoc will try to import control content from.
182+
183+
The example below shows three different places it can be set:
184+
top-level site config, section config, or in a page element.
185+
186+
```yaml
187+
# (1) package set on top-level site config
188+
quartodoc:
189+
package: quartodoc
190+
sections:
191+
- title: ""
192+
desc: ""
193+
contents:
194+
- get_object # quartodoc.get_object
195+
196+
# (2) package set on a section
197+
- title: ""
198+
desc: ""
199+
package: quartodoc.ast
200+
contents:
201+
- preview # quartodoc.ast.preview
202+
203+
# (3) package set on a page
204+
- kind: page
205+
package: pandas
206+
contents:
207+
- DataFrame # pandas.DataFrame
208+
209+
# (4) package set on individual content entry
210+
- package: pandas
211+
name: Series
212+
```
213+
214+
Use `package: null` to unset the package option. This enables you to specify objects using their full name.
215+
216+
```yaml
217+
quartodoc:
218+
package: quartodoc
219+
sections:
220+
- title: ""
221+
desc: ""
222+
package: null
223+
contents:
224+
- quartodoc.get_object
225+
```
226+
227+
228+
## Dynamic lookup
229+
230+
By default, quartodoc uses static analysis to look up objects.
231+
This means it gets information about your docstring without actually running your package's code.
232+
233+
This usually works well, but may get the docstring wrong for those created in an extremely dynamic way (e.g. you manually set the `__doc__` attribute on an object).
234+
235+
In this case, you can set the dynamic option on a piece of content.
236+
237+
```yaml
238+
contents:
239+
- name: get_object
240+
dynamic: true
241+
```
242+
243+
102244
## Reference page sub-sections
103245

104246
quartodoc supports two levels of grouping on the reference page.
@@ -210,67 +352,3 @@ Note these three important pieces of the page entry:
210352
* `contents:` - lists out the contents of the page.
211353

212354

213-
214-
## Setting default package path
215-
216-
Different levels of configuration let you set the `package` option.
217-
This controls the package path that quartodoc will try to import control content from.
218-
219-
The example below shows three different places it can be set:
220-
top-level site config, section config, or in a page element.
221-
222-
```yaml
223-
# (1) package set on top-level site config
224-
quartodoc:
225-
package: quartodoc
226-
sections:
227-
- title: ""
228-
desc: ""
229-
contents:
230-
- get_object # quartodoc.get_object
231-
232-
# (2) package set on a section
233-
- title: ""
234-
desc: ""
235-
package: quartodoc.ast
236-
contents:
237-
- preview # quartodoc.ast.preview
238-
239-
# (3) package set on a page
240-
- kind: page
241-
package: pandas
242-
contents:
243-
- DataFrame # pandas.DataFrame
244-
245-
# (4) package set on individual content entry
246-
- package: pandas
247-
name: Series
248-
```
249-
250-
Use `package: null` to unset the package option. This enables you to specify objects using their full name.
251-
252-
```yaml
253-
quartodoc:
254-
package: quartodoc
255-
sections:
256-
- title: ""
257-
desc: ""
258-
package: null
259-
contents:
260-
- quartodoc.get_object
261-
```
262-
263-
## Dynamic lookup
264-
265-
By default, quartodoc uses static analysis to look up objects.
266-
This means it gets information about your docstring without actually running your package's code.
267-
268-
This usually works well, but may get the docstring wrong for those created in an extremely dynamic way (e.g. you manually set the `__doc__` attribute on an object).
269-
270-
In this case, you can set the dynamic option on a piece of content.
271-
272-
```yaml
273-
contents:
274-
- name: get_object
275-
dynamic: true
276-
```

quartodoc/builder/blueprint.py

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,14 @@
2929

3030
from .utils import PydanticTransformer, ctx_node, WorkaroundKeyError
3131

32-
from typing import overload
32+
from typing import overload, TYPE_CHECKING
33+
3334

3435
_log = logging.getLogger(__name__)
3536

37+
if TYPE_CHECKING:
38+
from pydantic import BaseModel
39+
3640

3741
def _auto_package(mod: dc.Module) -> list[Section]:
3842
"""Create default sections for the given package."""
@@ -86,13 +90,24 @@ def _is_external_alias(obj: dc.Alias | dc.Object, mod: dc.Module):
8690
return False
8791

8892

89-
def _to_simple_dict(el):
93+
def _to_simple_dict(el: "BaseModel"):
9094
# round-trip to json, so we can take advantage of pydantic
9195
# dumping Enums, etc.. There may be a simple way to do
9296
# this in pydantic v2.
9397
return json.loads(el.json(exclude_unset=True))
9498

9599

100+
def _non_default_entries(el: "BaseModel"):
101+
field_defaults = {mf.name: mf.default for mf in el.__fields__.values()}
102+
set_fields = [
103+
k for k, v in el if field_defaults[k] is not v if not isinstance(v, MISSING)
104+
]
105+
106+
d = el.dict()
107+
108+
return {k: d[k] for k in set_fields}
109+
110+
96111
class BlueprintTransformer(PydanticTransformer):
97112
def __init__(self, get_object=None, parser="numpy"):
98113

@@ -107,7 +122,8 @@ def __init__(self, get_object=None, parser="numpy"):
107122
self.get_object = get_object
108123

109124
self.crnt_package = None
110-
self.dynamic = None
125+
self.options = None
126+
self.dynamic = False
111127

112128
@staticmethod
113129
def _append_member_path(path: str, new: str):
@@ -138,16 +154,26 @@ def visit(self, el):
138154
# TODO: use a context handler
139155
self._log("VISITING", el)
140156

157+
# set package ----
141158
package = getattr(el, "package", MISSING())
142159
old = self.crnt_package
143160

144161
if not isinstance(package, MISSING):
145162
self.crnt_package = package
146163

164+
# set options ----
165+
# TODO: check for Section instead?
166+
options = getattr(el, "options", None)
167+
old_options = self.options
168+
169+
if options is not None:
170+
self.options = options
171+
147172
try:
148173
return super().visit(el)
149174
finally:
150175
self.crnt_package = old
176+
self.options = old_options
151177

152178
@dispatch
153179
def enter(self, el: Layout):
@@ -208,6 +234,7 @@ def exit(self, el: Section):
208234
def enter(self, el: Auto):
209235
self._log("Entering", el)
210236

237+
# settings based on parent context options (package, options) ----
211238
# TODO: make this less brittle
212239
pkg = self.crnt_package
213240
if pkg is None:
@@ -217,6 +244,14 @@ def enter(self, el: Auto):
217244
else:
218245
path = f"{pkg}:{el.name}"
219246

247+
# auto default overrides
248+
if self.options is not None:
249+
# TODO: is this round-tripping guaranteed by pydantic?
250+
_option_dict = _non_default_entries(self.options)
251+
_el_dict = _non_default_entries(el)
252+
el = el.__class__(**{**_option_dict, **_el_dict})
253+
254+
# fetching object ----
220255
_log.info(f"Getting object for {path}")
221256

222257
dynamic = el.dynamic if el.dynamic is not None else self.dynamic

quartodoc/layout.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ class Section(_Structural):
7878
desc: Optional[str] = None
7979
package: Union[str, None, MISSING] = MISSING()
8080
contents: ContentList = []
81+
options: Optional["AutoOptions"] = None
8182

8283
def __init__(self, **data):
8384
super().__init__(**data)
@@ -199,7 +200,21 @@ class ChoicesChildren(Enum):
199200
linked = "linked"
200201

201202

202-
class Auto(_Base):
203+
class AutoOptions(_Base):
204+
"""Options available for Auto content layout element."""
205+
206+
members: Optional[list[str]] = None
207+
include_private: bool = False
208+
include_imports: bool = False
209+
include_empty: bool = False
210+
include: Optional[str] = None
211+
exclude: Optional[str] = None
212+
dynamic: Union[None, bool, str] = None
213+
children: ChoicesChildren = ChoicesChildren.embedded
214+
package: Union[str, None, MISSING] = MISSING()
215+
216+
217+
class Auto(AutoOptions):
203218
"""Configure a python object to document (e.g. module, class, function, attribute).
204219
205220
Attributes
@@ -233,15 +248,6 @@ class Auto(_Base):
233248

234249
kind: Literal["auto"] = "auto"
235250
name: str
236-
members: Optional[list[str]] = None
237-
include_private: bool = False
238-
include_imports: bool = False
239-
include_empty: bool = False
240-
include: Optional[str] = None
241-
exclude: Optional[str] = None
242-
dynamic: Union[None, bool, str] = None
243-
children: ChoicesChildren = ChoicesChildren.embedded
244-
package: Union[str, None, MISSING] = MISSING()
245251

246252

247253
# TODO: rename to Default or something

0 commit comments

Comments
 (0)