generated from brenozd/python-template
feat: Add reST docstring renderer support
• Introduce REST_DOCSTRING style in DocumentationStyle. • Implement ReSTDocstringRenderer for reStructuredText output. • Update renderers (Doxygen, Google, Numpy) to use examples instead of example field consistently. • Add exceptions field handling to GoogleDocstringRenderer, NumpyDocstringRenderer and DoxygenRenderer. • Update LLM constraints to refine example generation logic. • Map new style to the corresponding renderer.
This commit was merged in pull request #3.
This commit is contained in:
@@ -8,6 +8,7 @@ class DocumentationStyle(Enum):
|
||||
# Python
|
||||
GOOGLE_DOCSTRING = "Google DocString"
|
||||
NUMPY_DOCSTRING = "NumPy DocString"
|
||||
REST_DOCSTRING= "reST DocString"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.value
|
||||
|
||||
@@ -2,10 +2,12 @@ from .renderer import Renderer
|
||||
from .c_cpp_doxygen import DoxygenRenderer
|
||||
from .python_numpy import NumpyDocstringRenderer
|
||||
from .python_google import GoogleDocstringRenderer
|
||||
from .python_rest import ReSTDocstringRenderer
|
||||
|
||||
__all__ = [
|
||||
"Renderer",
|
||||
"DoxygenRenderer",
|
||||
"NumpyDocstringRenderer",
|
||||
"GoogleDocstringRenderer",
|
||||
"ReSTDocstringRenderer",
|
||||
]
|
||||
|
||||
@@ -31,7 +31,7 @@ class DoxygenRenderer(Renderer):
|
||||
self._doc.members
|
||||
or self._doc.return_info
|
||||
or self._doc.remarks
|
||||
or self._doc.example
|
||||
or self._doc.examples
|
||||
):
|
||||
lines.append(" *")
|
||||
|
||||
@@ -58,6 +58,12 @@ class DoxygenRenderer(Renderer):
|
||||
lines.append(f" * \\return {self._doc.return_info}")
|
||||
lines.append(" *")
|
||||
|
||||
# Exceptions
|
||||
for exc, cause in self._doc.exceptions.items():
|
||||
lines.append(f" * \\exception {exc} {cause}")
|
||||
if self._doc.exceptions:
|
||||
lines.append(" *")
|
||||
|
||||
# Remarks / notes
|
||||
for note in self._doc.remarks:
|
||||
lines.append(f" * \\note {note}")
|
||||
@@ -65,9 +71,9 @@ class DoxygenRenderer(Renderer):
|
||||
lines.append(" *")
|
||||
|
||||
# Examples
|
||||
if self._doc.example:
|
||||
if self._doc.examples:
|
||||
lines.append(" * \\code")
|
||||
for ex_line in self._doc.example:
|
||||
for ex_line in self._doc.examples:
|
||||
lines.append(f" * {ex_line}")
|
||||
lines.append(" * \\endcode")
|
||||
lines.append(" *")
|
||||
|
||||
@@ -15,7 +15,7 @@ class GoogleDocstringRenderer(Renderer):
|
||||
- Returns
|
||||
- Notes
|
||||
"""
|
||||
lines = [f'"""\n{self._doc.description}']
|
||||
lines = [f'"""{self._doc.description}']
|
||||
|
||||
# Add a blank line before sections if needed
|
||||
if self._doc.members or self._doc.return_info or self._doc.remarks:
|
||||
@@ -48,6 +48,11 @@ class GoogleDocstringRenderer(Renderer):
|
||||
lines.append(f" {self._doc.return_info}")
|
||||
lines.append("")
|
||||
|
||||
if len(self._doc.exceptions.items()) > 0:
|
||||
lines.append("Raises:")
|
||||
for name, case in self._doc.exceptions.items():
|
||||
lines.append(f" {name}: {case}")
|
||||
|
||||
# Notes or additional remarks
|
||||
if self._doc.remarks:
|
||||
lines.append("Notes:")
|
||||
|
||||
@@ -53,6 +53,13 @@ class NumpyDocstringRenderer(Renderer):
|
||||
lines.append(f"{self._doc.return_info.strip()}")
|
||||
lines.append("")
|
||||
|
||||
if len(self._doc.exceptions.items()) > 0:
|
||||
lines.append("Raises")
|
||||
lines.append("-------")
|
||||
for name, case in self._doc.exceptions.items():
|
||||
lines.append(f"{name.strip()}")
|
||||
lines.append(f" {case.strip()}")
|
||||
|
||||
# --- Notes Section ---
|
||||
if self._doc.remarks:
|
||||
lines.append("Notes")
|
||||
@@ -62,10 +69,10 @@ class NumpyDocstringRenderer(Renderer):
|
||||
lines.append("")
|
||||
|
||||
# --- Examples Section ---
|
||||
if self._doc.example:
|
||||
if self._doc.examples:
|
||||
lines.append("Examples")
|
||||
lines.append("--------")
|
||||
for ex_line in self._doc.example:
|
||||
for ex_line in self._doc.examples:
|
||||
lines.append(f" {ex_line}")
|
||||
lines.append("")
|
||||
|
||||
|
||||
75
src/docai/doc_renderers/python_rest.py
Normal file
75
src/docai/doc_renderers/python_rest.py
Normal file
@@ -0,0 +1,75 @@
|
||||
from docai.doc_renderers.renderer import Renderer
|
||||
|
||||
|
||||
class ReSTDocstringRenderer(Renderer):
|
||||
"""Renderer that outputs reStructuredText (reST/Sphinx) docstrings from a DocModel."""
|
||||
|
||||
def render(self) -> str:
|
||||
lines = ['"""']
|
||||
lines.append(self._doc.description.strip())
|
||||
lines.append("") # blank line after summary
|
||||
|
||||
kind = str(self._doc.entity_kind).lower()
|
||||
|
||||
# --- Attributes / Parameters / Enum Values ---
|
||||
if self._doc.members:
|
||||
if kind in ("class", "struct"):
|
||||
for member in self._doc.members:
|
||||
type_info = member.data_type or "Any"
|
||||
lines.append(f".. attribute:: {member.name}")
|
||||
lines.append(f" :type: {type_info}")
|
||||
lines.append("")
|
||||
lines.append(f" {member.description}")
|
||||
lines.append("")
|
||||
|
||||
elif kind == "function":
|
||||
for member in self._doc.members:
|
||||
type_info = member.data_type or "Any"
|
||||
lines.append(f":param {member.name}: {member.description}")
|
||||
lines.append(f":type {member.name}: {type_info}")
|
||||
lines.append("")
|
||||
|
||||
elif kind == "enum":
|
||||
for member in self._doc.members:
|
||||
lines.append(f".. data:: {member.name}")
|
||||
lines.append(f" {member.description}")
|
||||
lines.append("")
|
||||
|
||||
# --- Returns ---
|
||||
if self._doc.return_info:
|
||||
# Split type & description if provided as "type: description"
|
||||
if ":" in self._doc.return_info:
|
||||
ret_type, desc = self._doc.return_info.split(":", 1)
|
||||
lines.append(f":return: {desc.strip()}")
|
||||
lines.append(f":rtype: {ret_type.strip()}")
|
||||
else:
|
||||
# Only description OR only type
|
||||
lines.append(f":return: {self._doc.return_info.strip()}")
|
||||
lines.append("")
|
||||
|
||||
# --- Exceptions ---
|
||||
if len(self._doc.exceptions.items()) > 0:
|
||||
for name, case in self._doc.exceptions.items():
|
||||
lines.append(f":raises {name}: {case.strip()}")
|
||||
lines.append("")
|
||||
|
||||
# --- Notes ---
|
||||
if self._doc.remarks:
|
||||
lines.append(".. note::")
|
||||
for note in self._doc.remarks:
|
||||
lines.append(f" {note}")
|
||||
lines.append("")
|
||||
|
||||
# --- Examples ---
|
||||
if self._doc.examples:
|
||||
lines.append("Examples")
|
||||
lines.append("--------")
|
||||
lines.append("")
|
||||
lines.append(".. code-block:: python")
|
||||
lines.append("")
|
||||
for ex in self._doc.examples:
|
||||
lines.append(f" {ex}")
|
||||
lines.append("")
|
||||
|
||||
lines.append('"""')
|
||||
return "\n".join(lines)
|
||||
@@ -55,8 +55,14 @@ Constraints:
|
||||
- Avoid technical jargon unless necessary for the target audience
|
||||
- Focus on user intent: "What problem does this solve?" not "How does it work?"
|
||||
- Do not guess the meaning of any acronyms.
|
||||
- Do not guess meanings of acronyms. Only provide a definition if it is standard and unambiguous.
|
||||
- Only provide meanings that are explicitly stated or widely known standard definitions.
|
||||
- If the acronym's meaning is not present in the provided text, or if it could have multiple interpretations, do NOT invent a definition.
|
||||
- Examples must NOT be included automatically.
|
||||
- Only generate an example when the entity is non-trivial, meaning at least one of the following is true:
|
||||
- The function/class performs multiple conceptual steps.
|
||||
- The behavior is not obvious from its name or signature.
|
||||
- The entity has side effects, unusual constraints, or ordering requirements.
|
||||
- The typical usage is not clear from the description alone.
|
||||
"""
|
||||
|
||||
user_context = f"Expert Technical Writer specializing in {lang} documentation"
|
||||
|
||||
@@ -5,4 +5,5 @@ RENDERERS_BY_STYLE = {
|
||||
DocumentationStyle.DOXYGEN: docai.doc_renderers.DoxygenRenderer,
|
||||
DocumentationStyle.GOOGLE_DOCSTRING: docai.doc_renderers.GoogleDocstringRenderer,
|
||||
DocumentationStyle.NUMPY_DOCSTRING: docai.doc_renderers.NumpyDocstringRenderer,
|
||||
DocumentationStyle.REST_DOCSTRING: docai.doc_renderers.ReSTDocstringRenderer,
|
||||
}
|
||||
|
||||
@@ -26,52 +26,58 @@ class EntitySource(BaseModel):
|
||||
f"text={repr(self.text)}, documentation={repr(self.documentation)})"
|
||||
)
|
||||
|
||||
|
||||
class MemberDoc(BaseModel):
|
||||
name: str = Field(
|
||||
...,
|
||||
description="The identifier of the member, parameter, or field. This is the variable or parameter name as it appears in the code."
|
||||
description="The identifier of the member, parameter, or field. This is the variable or parameter name as it appears in the code.",
|
||||
)
|
||||
is_generic: bool = Field(
|
||||
default=False,
|
||||
description="Indicates whether this member is a generic/template type (e.g., C++ template parameter or Java generic). Helps the LLM explain type flexibility."
|
||||
description="Indicates whether this member is a generic/template type (e.g., C++ template parameter or Java generic). Helps the LLM explain type flexibility.",
|
||||
)
|
||||
data_type: str | None = Field(
|
||||
None,
|
||||
description="The data type of the member, parameter, or field (e.g., int, string, List[str]). Used by the LLM to describe the type explicitly in documentation."
|
||||
description="The data type of the member, parameter, or field (e.g., int, string, List[str]). Used by the LLM to describe the type explicitly in documentation.",
|
||||
)
|
||||
description: str = Field(
|
||||
...,
|
||||
description="A concise explanation of the member/parameter/field. The LLM should summarize its purpose, role, or constraints in one sentence."
|
||||
description="A concise explanation of the member/parameter/field. The LLM should summarize its purpose, role, or constraints in one sentence.",
|
||||
)
|
||||
|
||||
|
||||
class EntityDoc(BaseModel):
|
||||
name: str = Field(
|
||||
...,
|
||||
description="The name of the entity being documented (class, function, method, union, struct, enum, module, etc.). The LLM should use this name consistently in examples and descriptions."
|
||||
description="The name of the entity being documented (class, function, method, union, struct, enum, module, etc.). The LLM should use this name consistently in examples and descriptions.",
|
||||
)
|
||||
entity_kind: CodeEntityKind = Field(
|
||||
...,
|
||||
description="The high-level type of the entity (e.g., 'function', 'class', 'struct'). Guides the LLM on how to structure the documentation and which Doxygen tags to use."
|
||||
description="The high-level type of the entity (e.g., 'function', 'class', 'struct'). Guides the LLM on how to structure the documentation and which Doxygen tags to use.",
|
||||
)
|
||||
description: str = Field(
|
||||
...,
|
||||
description="A one-sentence summary of the entity’s purpose, behavior, or role. Typically serves as the \\brief description in generated Doxygen comments."
|
||||
description="A one-sentence summary of the entity’s purpose, behavior, or role. Typically serves as the \\brief description in generated Doxygen comments.",
|
||||
)
|
||||
members: list[MemberDoc] = Field(
|
||||
default_factory=list,
|
||||
description="A list of members, parameters, or fields associated with this entity. For functions, these are parameters; for classes/structs/unions, these are fields; for enums, these are enumerators. The LLM should describe each member clearly with its type and purpose."
|
||||
description="A list of members, parameters, or fields associated with this entity. For functions, these are parameters; for classes/structs/unions, these are fields; for enums, these are enumerators. The LLM should describe each member clearly with its type and purpose.",
|
||||
)
|
||||
return_info: str | None = Field(
|
||||
None,
|
||||
description="Documentation for the return value of the entity (only for functions or methods). The LLM should explain the semantics, possible edge cases, and any ownership or allocation notes."
|
||||
description="Documentation for the return value of the entity (only for functions or methods). The LLM should explain the semantics, possible edge cases, and any ownership or allocation notes.",
|
||||
)
|
||||
exceptions: dict[str, str] = Field(
|
||||
default_factory=dict,
|
||||
description="Exceptions raised if any in the format (exception) -> (when it occurs)",
|
||||
)
|
||||
remarks: list[str] = Field(
|
||||
default_factory=list,
|
||||
description="Additional notes, warnings, usage hints, constraints, or examples that don’t fit in the main description. Each entry should be rendered as a separate note or paragraph by the LLM."
|
||||
description="Additional notes, warnings, usage hints, constraints, or examples that don’t fit in the main description. Each entry should be rendered as a separate note or paragraph by the LLM.",
|
||||
)
|
||||
example: list[str] | None = Field(
|
||||
examples: list[str] | None = Field(
|
||||
None,
|
||||
description="Concrete code examples demonstrating usage of the entity. For methods or classes, show how it is called or instantiated. Must be None for internal or trivial entities such as main methods."
|
||||
description="Concrete code examples demonstrating usage of the entity. For methods or classes, show how it is called or instantiated. Must be None for internal or trivial entities such as main methods.",
|
||||
)
|
||||
|
||||
@validator("entity_kind", pre=True)
|
||||
|
||||
Reference in New Issue
Block a user