From 0463b7e45eea11313fa4f7db244c13744901f0bd Mon Sep 17 00:00:00 2001 From: Michael Chow Date: Tue, 27 Aug 2024 18:49:53 -0400 Subject: [PATCH 1/8] fix: missing branch in show_signature_annotations --- quartodoc/renderers/md_renderer.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/quartodoc/renderers/md_renderer.py b/quartodoc/renderers/md_renderer.py index 583071e..ee2aa6c 100644 --- a/quartodoc/renderers/md_renderer.py +++ b/quartodoc/renderers/md_renderer.py @@ -539,6 +539,9 @@ def render(self, el: dc.Parameter): res = f"{glob}{name}: {annotation} = {el.default}" elif annotation: res = f"{glob}{name}: {annotation}" + else: + res = f"{glob}{name}" + elif has_default: res = f"{glob}{name}={el.default}" else: From 1dff27cf39230e33a839a84ad710f37bf40940fb Mon Sep 17 00:00:00 2001 From: Michael Chow Date: Wed, 9 Oct 2024 12:54:56 -0400 Subject: [PATCH 2/8] feat: implement auto excludes option --- quartodoc/builder/blueprint.py | 12 ++++++------ quartodoc/layout.py | 6 ++++-- quartodoc/tests/test_builder_blueprint.py | 24 +++++++++++++++++++++++ 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/quartodoc/builder/blueprint.py b/quartodoc/builder/blueprint.py index 10786c7..0d0ff86 100644 --- a/quartodoc/builder/blueprint.py +++ b/quartodoc/builder/blueprint.py @@ -371,12 +371,6 @@ def _fetch_members(self, el: Auto, obj: dc.Object | dc.Alias): if obj.is_module and obj.exports is not None: options = {k: v for k, v in options.items() if v.is_exported} - if el.include: - raise NotImplementedError("include argument currently unsupported.") - - if el.exclude: - raise NotImplementedError("exclude argument currently unsupported.") - if not el.include_private: options = {k: v for k, v in options.items() if not k.startswith("_")} @@ -407,6 +401,12 @@ def _fetch_members(self, el: Auto, obj: dc.Object | dc.Alias): if not el.include_functions: options = {k: v for k, v in options.items() if not v.is_function} + if el.include: + raise NotImplementedError("include argument currently unsupported.") + + if el.exclude: + options = {k: v for k, v in options.items() if k not in el.exclude} + return sorted(options) diff --git a/quartodoc/layout.py b/quartodoc/layout.py index 43cdf8e..84f2a1d 100644 --- a/quartodoc/layout.py +++ b/quartodoc/layout.py @@ -221,7 +221,7 @@ class AutoOptions(_Base): # other options ---- include: Optional[str] = None - exclude: Optional[str] = None + exclude: Optional[list[str]] = None dynamic: Union[None, bool, str] = None children: ChoicesChildren = ChoicesChildren.embedded package: Union[str, None, MISSING] = MISSING() @@ -249,6 +249,7 @@ class Auto(AutoOptions): path the object, and short is the name of the object (i.e. no periods). members: A list of members, such as attributes or methods on a class, to document. + If members is specified, no other includes or excludes are applied. include_private: Whether to include members starting with "_" include_imports: @@ -266,7 +267,8 @@ class Auto(AutoOptions): include: (Not implemented). A list of members to include. exclude: - (Not implemented). A list of members to exclude. + A list of members to exclude. This is performed last, in order to subtract + from the results of options like include_functions. dynamic: Whether to dynamically load docstring. By default docstrings are loaded using static analysis. dynamic may be a string pointing to another object, diff --git a/quartodoc/tests/test_builder_blueprint.py b/quartodoc/tests/test_builder_blueprint.py index bd6c4cc..927ddb0 100644 --- a/quartodoc/tests/test_builder_blueprint.py +++ b/quartodoc/tests/test_builder_blueprint.py @@ -245,6 +245,30 @@ def test_blueprint_fetch_members_include_inherited(): assert "some_method" in member_names +def test_blueprint_fetch_members_exclude(): + auto = lo.Auto( + name="quartodoc.tests.example_class.C", + include_functions=True, + include_attributes=False, + include_classes=False, + exclude=["some_property", "some_class_method"], + ) + + bp = blueprint(auto) + _check_member_names(bp.members, {"some_method"}) + + +def test_blueprint_fetch_members_exclude_ignored(): + auto = lo.Auto( + name="quartodoc.tests.example_class.C", + members=["some_property", "some_class_method"], + exclude=["some_class_method"], + ) + + bp = blueprint(auto) + _check_member_names(bp.members, {"some_property", "some_class_method"}) + + def test_blueprint_fetch_members_dynamic(): # Since AClass is imported via star import it has to be dynamically # resolved. This test ensures that the members of AClass also get From 04c1b17fdc827f6f04b1310a90c2659a2af3ba2c Mon Sep 17 00:00:00 2001 From: Michael Chow Date: Wed, 9 Oct 2024 14:23:35 -0400 Subject: [PATCH 3/8] feat: add Auto member_order option --- quartodoc/builder/blueprint.py | 7 ++++++- quartodoc/layout.py | 4 ++++ quartodoc/tests/test_builder_blueprint.py | 19 +++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/quartodoc/builder/blueprint.py b/quartodoc/builder/blueprint.py index 10786c7..761f10d 100644 --- a/quartodoc/builder/blueprint.py +++ b/quartodoc/builder/blueprint.py @@ -407,7 +407,12 @@ def _fetch_members(self, el: Auto, obj: dc.Object | dc.Alias): if not el.include_functions: options = {k: v for k, v in options.items() if not v.is_function} - return sorted(options) + if el.member_order == "alphabetical": + return sorted(options) + elif el.member_order == "source": + return list(options) + else: + raise ValueError(f"Unsupported value of member_order: {el.member_order}") class _PagePackageStripper(PydanticTransformer): diff --git a/quartodoc/layout.py b/quartodoc/layout.py index 43cdf8e..4e3a1a2 100644 --- a/quartodoc/layout.py +++ b/quartodoc/layout.py @@ -225,6 +225,7 @@ class AutoOptions(_Base): dynamic: Union[None, bool, str] = None children: ChoicesChildren = ChoicesChildren.embedded package: Union[str, None, MISSING] = MISSING() + member_order: Literal["alphabetical", "source"] = "alphabetical" member_options: Optional["AutoOptions"] = None # for tracking fields users manually specify @@ -275,6 +276,9 @@ class Auto(AutoOptions): Style for presenting members. Either separate, embedded, or flat. package: If specified, object lookup will be relative to this path. + member_order: + Order to present members in, either "alphabetical" or "source" order. + Defaults to alphabetical sorting. member_options: Options to apply to members. These can include any of the options above. diff --git a/quartodoc/tests/test_builder_blueprint.py b/quartodoc/tests/test_builder_blueprint.py index bd6c4cc..d7e7c28 100644 --- a/quartodoc/tests/test_builder_blueprint.py +++ b/quartodoc/tests/test_builder_blueprint.py @@ -260,6 +260,25 @@ def test_blueprint_fetch_members_dynamic(): assert bp.members[0].obj.parent.path == name.replace(":", ".") +@pytest.mark.parametrize("order", ["alphabetical", "source"]) +def test_blueprint_member_order(order): + auto = lo.Auto( + name="quartodoc.tests.example_class.C", + member_order=order, + include_functions=True, + include_attributes=False, + include_classes=False, + ) + bp = blueprint(auto) + src_names = [entry.name for entry in bp.members] + dst_names = ["some_method", "some_class_method"] + + if order == "alphabetical": + dst_names = sorted(dst_names) + + assert src_names == dst_names + + def test_blueprint_member_options(): auto = lo.Auto( name="quartodoc.tests.example", From 6c5ec48ff204c51e94be43146ba806499fee0642 Mon Sep 17 00:00:00 2001 From: Michael Chow Date: Wed, 9 Oct 2024 16:18:58 -0400 Subject: [PATCH 4/8] fix: parameter description list do not render None as default --- quartodoc/renderers/md_renderer.py | 2 +- quartodoc/tests/__snapshots__/test_renderers.ambr | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/quartodoc/renderers/md_renderer.py b/quartodoc/renderers/md_renderer.py index 905f154..90b3c7e 100644 --- a/quartodoc/renderers/md_renderer.py +++ b/quartodoc/renderers/md_renderer.py @@ -77,7 +77,7 @@ def to_definition_list(self): # in the table display format, not description lists.... # by this stage _required_ is basically a special token to indicate # a required argument. - if default is not None: + if self.default is not None: part_default_sep = Span(" = ", Attr(classes=["parameter-default-sep"])) part_default = Span(default, Attr(classes=["parameter-default"])) else: diff --git a/quartodoc/tests/__snapshots__/test_renderers.ambr b/quartodoc/tests/__snapshots__/test_renderers.ambr index 2197110..367a787 100644 --- a/quartodoc/tests/__snapshots__/test_renderers.ambr +++ b/quartodoc/tests/__snapshots__/test_renderers.ambr @@ -483,19 +483,19 @@ List # Parameters {.doc-section .doc-section-parameters} - [**int**]{.parameter-name} [:]{.parameter-annotation-sep} []{.parameter-annotation} [ = ]{.parameter-default-sep} [None]{.parameter-default} + [**int**]{.parameter-name} [:]{.parameter-annotation-sep} []{.parameter-annotation} : A description. # Returns {.doc-section .doc-section-returns} - []{.parameter-name} [:]{.parameter-annotation-sep} [int]{.parameter-annotation} [ = ]{.parameter-default-sep} [None]{.parameter-default} + []{.parameter-name} [:]{.parameter-annotation-sep} [int]{.parameter-annotation} : A description. # Attributes {.doc-section .doc-section-attributes} - [**int**]{.parameter-name} [:]{.parameter-annotation-sep} []{.parameter-annotation} [ = ]{.parameter-default-sep} [None]{.parameter-default} + [**int**]{.parameter-name} [:]{.parameter-annotation-sep} []{.parameter-annotation} : A description. ''' @@ -540,19 +540,19 @@ List # Parameters {.doc-section .doc-section-parameters} - [**name**]{.parameter-name} [:]{.parameter-annotation-sep} []{.parameter-annotation} [ = ]{.parameter-default-sep} [None]{.parameter-default} + [**name**]{.parameter-name} [:]{.parameter-annotation-sep} []{.parameter-annotation} : A description. # Returns {.doc-section .doc-section-returns} - [**name**]{.parameter-name} [:]{.parameter-annotation-sep} [int]{.parameter-annotation} [ = ]{.parameter-default-sep} [None]{.parameter-default} + [**name**]{.parameter-name} [:]{.parameter-annotation-sep} [int]{.parameter-annotation} : A description. # Attributes {.doc-section .doc-section-attributes} - [**name**]{.parameter-name} [:]{.parameter-annotation-sep} [int]{.parameter-annotation} [ = ]{.parameter-default-sep} [None]{.parameter-default} + [**name**]{.parameter-name} [:]{.parameter-annotation-sep} [int]{.parameter-annotation} : A description. ''' From 69158d95fb76c051897af3972284dc703bcc95cc Mon Sep 17 00:00:00 2001 From: Michael Chow Date: Wed, 9 Oct 2024 16:35:33 -0400 Subject: [PATCH 5/8] refactor!: move sanitize description to inside ParamRow --- quartodoc/renderers/md_renderer.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/quartodoc/renderers/md_renderer.py b/quartodoc/renderers/md_renderer.py index 90b3c7e..0d3c871 100644 --- a/quartodoc/renderers/md_renderer.py +++ b/quartodoc/renderers/md_renderer.py @@ -59,7 +59,7 @@ class ParamRow: def to_definition_list(self): name = self.name anno = self.annotation - desc = self.description + desc = sanitize(self.description, allow_markdown=True) default = sanitize(str(self.default)) part_name = ( @@ -100,13 +100,15 @@ def to_definition_list(self): def to_tuple(self, style: Literal["parameters", "attributes", "returns"]): name = self.name + description = sanitize(self.description, allow_markdown=True) + if style == "parameters": default = "_required_" if self.default is None else escape(self.default) - return (name, self.annotation, self.description, default) + return (name, self.annotation, description, default) elif style == "attributes": - return (name, self.annotation, self.description) + return (name, self.annotation, description) elif style == "returns": - return (name, self.annotation, self.description) + return (name, self.annotation, description) raise NotImplementedError(f"Unsupported table style: {style}") @@ -595,8 +597,9 @@ def render(self, el: ds.DocstringSectionParameters): @dispatch def render(self, el: ds.DocstringParameter) -> ParamRow: annotation = self.render_annotation(el.annotation) - clean_desc = sanitize(el.description, allow_markdown=True) - return ParamRow(el.name, clean_desc, annotation=annotation, default=el.default) + return ParamRow( + el.name, el.description, annotation=annotation, default=el.default + ) # attributes ---- @@ -611,7 +614,7 @@ def render(self, el: ds.DocstringSectionAttributes): def render(self, el: ds.DocstringAttribute) -> ParamRow: return ParamRow( el.name, - sanitize(el.description or "", allow_markdown=True), + el.description or "", annotation=self.render_annotation(el.annotation), ) @@ -680,7 +683,7 @@ def render(self, el: ds.DocstringReturn): # similar to DocstringParameter, but no name or default return ParamRow( el.name, - sanitize(el.description, allow_markdown=True), + el.description, annotation=self.render_annotation(el.annotation), ) @@ -689,7 +692,7 @@ def render(self, el: ds.DocstringRaise) -> ParamRow: # similar to DocstringParameter, but no name or default return ParamRow( None, - sanitize(el.description, allow_markdown=True), + el.description, annotation=self.render_annotation(el.annotation), ) From b9b0153dea9861540f0d4cb1995e0744f43d91c6 Mon Sep 17 00:00:00 2001 From: Michael Chow Date: Thu, 10 Oct 2024 13:05:28 -0400 Subject: [PATCH 6/8] tests: include unannotated parameter in snapshot test --- .../tests/__snapshots__/test_renderers.ambr | 20 +++++++++++-------- quartodoc/tests/example_signature.py | 4 +++- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/quartodoc/tests/__snapshots__/test_renderers.ambr b/quartodoc/tests/__snapshots__/test_renderers.ambr index a46143e..d2dec21 100644 --- a/quartodoc/tests/__snapshots__/test_renderers.ambr +++ b/quartodoc/tests/__snapshots__/test_renderers.ambr @@ -7,15 +7,17 @@ tests.example_signature.a_complex_signature( x: list[C | int | None] y: pathlib.Pathlib + z ) ``` ## Parameters {.doc-section .doc-section-parameters} - | Name | Type | Description | Default | - |--------|--------------------------------------------------------------------------------------|-----------------|------------| - | x | [list](`list`)\[[C](`quartodoc.tests.example_signature.C`) \| [int](`int`) \| None\] | The x parameter | _required_ | - | y | [pathlib](`pathlib`).[Pathlib](`pathlib.Pathlib`) | The y parameter | _required_ | + | Name | Type | Description | Default | + |--------|--------------------------------------------------------------------------------------|-------------------------------|------------| + | x | [list](`list`)\[[C](`quartodoc.tests.example_signature.C`) \| [int](`int`) \| None\] | The x parameter | _required_ | + | y | [pathlib](`pathlib`).[Pathlib](`pathlib.Pathlib`) | The y parameter | _required_ | + | z | | The z parameter (unannotated) | _required_ | ''' # --- # name: test_render_annotations_complex_no_interlinks @@ -26,15 +28,17 @@ tests.example_signature.a_complex_signature( x: list[C | int | None] y: pathlib.Pathlib + z ) ``` ## Parameters {.doc-section .doc-section-parameters} - | Name | Type | Description | Default | - |--------|--------------------------|-----------------|------------| - | x | list\[C \| int \| None\] | The x parameter | _required_ | - | y | pathlib.Pathlib | The y parameter | _required_ | + | Name | Type | Description | Default | + |--------|--------------------------|-------------------------------|------------| + | x | list\[C \| int \| None\] | The x parameter | _required_ | + | y | pathlib.Pathlib | The y parameter | _required_ | + | z | | The z parameter (unannotated) | _required_ | ''' # --- # name: test_render_doc_class[embedded] diff --git a/quartodoc/tests/example_signature.py b/quartodoc/tests/example_signature.py index 0e58ef3..9a23b6a 100644 --- a/quartodoc/tests/example_signature.py +++ b/quartodoc/tests/example_signature.py @@ -31,7 +31,7 @@ class C: ... -def a_complex_signature(x: "list[C | int | None]", y: "pathlib.Pathlib"): +def a_complex_signature(x: "list[C | int | None]", y: "pathlib.Pathlib", z): """ Parameters ---------- @@ -39,4 +39,6 @@ def a_complex_signature(x: "list[C | int | None]", y: "pathlib.Pathlib"): The x parameter y: The y parameter + z: + The z parameter (unannotated) """ From 9bf1ec1b73d1c8a03258f1ac00820c3765b4da48 Mon Sep 17 00:00:00 2001 From: Michael Chow Date: Thu, 10 Oct 2024 13:09:01 -0400 Subject: [PATCH 7/8] ci: fix python version to 3.10 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ed7f07..58cb1ca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,7 +56,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 with: - python-version: "${{ matrix.python-version }}" + python-version: "3.10" - name: Install dev dependencies run: | python -m pip install --upgrade pip From 48245337914712e6320152d2defd8ee3db9bf987 Mon Sep 17 00:00:00 2001 From: Michael Chow Date: Fri, 11 Oct 2024 12:57:56 -0400 Subject: [PATCH 8/8] fix: description list no longer escapes quotes inside code --- quartodoc/renderers/base.py | 7 ++-- quartodoc/renderers/md_renderer.py | 4 +-- .../tests/__snapshots__/test_renderers.ambr | 32 +++++++++---------- quartodoc/tests/test_renderers.py | 2 +- 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/quartodoc/renderers/base.py b/quartodoc/renderers/base.py index d8a8968..7dea074 100644 --- a/quartodoc/renderers/base.py +++ b/quartodoc/renderers/base.py @@ -13,12 +13,15 @@ def escape(val: str): return f"`{val}`" -def sanitize(val: str, allow_markdown=False): +def sanitize(val: str, allow_markdown=False, escape_quotes=False): # sanitize common tokens that break tables res = val.replace("\n", " ").replace("|", "\\|") # sanitize elements that get turned into smart quotes - res = res.replace("'", r"\'").replace('"', r"\"") + # this is to avoid defaults that are strings having their + # quotes screwed up. + if escape_quotes: + res = res.replace("'", r"\'").replace('"', r"\"") # sanitize elements that can get interpreted as markdown links # or citations diff --git a/quartodoc/renderers/md_renderer.py b/quartodoc/renderers/md_renderer.py index 1193a2b..51cbd87 100644 --- a/quartodoc/renderers/md_renderer.py +++ b/quartodoc/renderers/md_renderer.py @@ -60,7 +60,7 @@ def to_definition_list(self): name = self.name anno = self.annotation desc = sanitize(self.description, allow_markdown=True) - default = sanitize(str(self.default)) + default = sanitize(str(self.default), escape_quotes=True) part_name = ( Span(Strong(name), Attr(classes=["parameter-name"])) @@ -219,7 +219,7 @@ def render_annotation(self, el: str) -> str: el: An object representing a type annotation. """ - return sanitize(el) + return sanitize(el, escape_quotes=True) @dispatch def render_annotation(self, el: None) -> str: diff --git a/quartodoc/tests/__snapshots__/test_renderers.ambr b/quartodoc/tests/__snapshots__/test_renderers.ambr index d6de891..0d99ba9 100644 --- a/quartodoc/tests/__snapshots__/test_renderers.ambr +++ b/quartodoc/tests/__snapshots__/test_renderers.ambr @@ -504,60 +504,60 @@ : A description. ''' # --- -# name: test_render_numpydoc_section_return[name: int\n A description.] +# name: test_render_numpydoc_section_return[name: int\n A `"description"`.] ''' Code Parameters --- name: int - A description. + A `"description"`. Returns --- name: int - A description. + A `"description"`. Attributes --- name: int - A description. + A `"description"`. Default # Parameters {.doc-section .doc-section-parameters} - | Name | Type | Description | Default | - |--------|--------|----------------|------------| - | name | | A description. | _required_ | + | Name | Type | Description | Default | + |--------|--------|--------------------|------------| + | name | | A `"description"`. | _required_ | # Returns {.doc-section .doc-section-returns} - | Name | Type | Description | - |--------|--------|----------------| - | name | int | A description. | + | Name | Type | Description | + |--------|--------|--------------------| + | name | int | A `"description"`. | # Attributes {.doc-section .doc-section-attributes} - | Name | Type | Description | - |--------|--------|----------------| - | name | int | A description. | + | Name | Type | Description | + |--------|--------|--------------------| + | name | int | A `"description"`. | List # Parameters {.doc-section .doc-section-parameters} [**name**]{.parameter-name} [:]{.parameter-annotation-sep} []{.parameter-annotation} - : A description. + : A `"description"`. # Returns {.doc-section .doc-section-returns} [**name**]{.parameter-name} [:]{.parameter-annotation-sep} [int]{.parameter-annotation} - : A description. + : A `"description"`. # Attributes {.doc-section .doc-section-attributes} [**name**]{.parameter-name} [:]{.parameter-annotation-sep} [int]{.parameter-annotation} - : A description. + : A `"description"`. ''' # --- diff --git a/quartodoc/tests/test_renderers.py b/quartodoc/tests/test_renderers.py index 922318d..8117fcf 100644 --- a/quartodoc/tests/test_renderers.py +++ b/quartodoc/tests/test_renderers.py @@ -223,7 +223,7 @@ def test_render_doc_signature_name_alias_of_alias(snapshot, renderer): @pytest.mark.parametrize( "doc", [ - """name: int\n A description.""", + """name: int\n A `"description"`.""", """int\n A description.""", ], )