Skip to content

Conversation

jinnovation
Copy link
Contributor

@jinnovation jinnovation commented May 1, 2025

Contributes to #793.

This PR implements quick-and-dirty support for Pydantic-style default values. In other words, the following two classes will render equivalently:

class Foo(pydantic.BaseModel):
    """Foo class documentation."""

    a: int = pydantic.Field(default=1, description="Docstring for a")

class Foo(pydantic.BaseModel):
    """Foo class documentation."""

    a: int = 1
    """Docstring for a"""

Open questions:

  • Should Pydantic support for pdoc be split out into a separate "plugin" package, e.g. pdoc-pydantic or pdoc[pydantic]?
  • Is this the best place for default-value-extraction logic for Pydantic classes to live?

Copy link
Member

@mhils mhils left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

Should Pydantic support for pdoc be split out into a separate "plugin" package, e.g. pdoc-pydantic or pdoc[pydantic]?

Too much complexity for now. I think pydantic is popular enough to have support for it builtin for now.

Is this the best place for default-value-extraction logic for Pydantic classes to live?

Pragmatically yes. See my comments below on how we can keep things reasonably clean.

@jinnovation jinnovation marked this pull request as draft May 2, 2025 17:23
@jinnovation

This comment was marked as resolved.

@jinnovation
Copy link
Contributor Author

Two issues related to rendering Pydantic model docs that I propose treating as out-of-scope for this PR:

  • If user's BaseModel subclass does not have its own class-level docstring, we end up rendering the docs for BaseModel itself;
  • We render the docs for model_config, which is typically only useful to implementers of the class and not to users thereof, i.e. the expected audience for the generated docs
image

pdoc/doc.py Outdated
Comment on lines 262 to 275
# For Pydantic models, filter out all methods on the BaseModel
# class, as they are almost never relevant to the consumers of the
# inheriting model itself.
if (
_pydantic._PYDANTIC_ENABLED
and self.kind == "class"
and _pydantic.is_pydantic_model(self.obj)
and (
name in _pydantic._IGNORED_FIELDS
or taken_from[0].startswith("pydantic")
)
):
continue

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mhils: Let me know what you think of this approach. It kind of contradicts the precedent members() sets of deferring all filtering/exclusion logic to the downstream templates.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's not the end of the world, but we could maybe move it into Class._member_objects? That already has some special cases for constructors. Adding another special case for pydantic there makes more sense maybe.

@jinnovation jinnovation marked this pull request as ready for review October 11, 2025 09:42
@jinnovation jinnovation requested a review from mhils October 11, 2025 19:36
Copy link
Member

@mhils mhils left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! This looks great overall. Mostly some minor stylistic nits.

pdoc/doc.py Outdated
Comment on lines 262 to 275
# For Pydantic models, filter out all methods on the BaseModel
# class, as they are almost never relevant to the consumers of the
# inheriting model itself.
if (
_pydantic._PYDANTIC_ENABLED
and self.kind == "class"
and _pydantic.is_pydantic_model(self.obj)
and (
name in _pydantic._IGNORED_FIELDS
or taken_from[0].startswith("pydantic")
)
):
continue

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's not the end of the world, but we could maybe move it into Class._member_objects? That already has some special cases for constructors. Adding another special case for pydantic there makes more sense maybe.

@@ -0,0 +1,7 @@
<module with_pydantic # A small example with…
<class with_pydantic.Foo # Foo class documentat…
<var a: int = 1>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not seem to be working yet? :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤨 could swear this was working before.

Either way, will look into. Moving this back to draft.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I'm trying to do two things at once. (:

This PR started off just handling Pydantic default values, but I've since expanded scope to also include descriptions.

Will revise PR title + description + all that before opening back up for review.

@jinnovation jinnovation marked this pull request as draft October 12, 2025 03:08
Comment on lines +341 to +346
if (
_pydantic.pydantic is not None
and self.kind == "class"
and _pydantic.is_pydantic_model(self.obj)
):
_docstring = _pydantic.get_field_docstring(self.obj, name)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consolidate conditional logic into _pydantic like w/ default_value().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants