Skip to content

Commit

Permalink
implement at comments
Browse files Browse the repository at this point in the history
  • Loading branch information
stevenbal committed Jan 9, 2025
1 parent 7313246 commit b802dc5
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 20 deletions.
64 changes: 45 additions & 19 deletions django_setup_configuration/documentation/model_directive.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,43 @@
import importlib
from typing import Annotated, Any, Dict, Type, Union, get_args, get_origin

import yaml
import ruamel.yaml
from docutils import nodes
from docutils.parsers.rst import Directive
from pydantic import BaseModel
from pydantic_core import PydanticUndefined
from ruamel.yaml.comments import CommentedMap


def generate_model_example(model: Type[BaseModel]) -> Dict[str, Any]:
example_data = {}
def generate_model_example(model: Type[BaseModel], depth=0) -> Dict[str, Any]:
example_data = CommentedMap()

# Loop through the annotations of the model to create example data
for field_name, field_type in model.__annotations__.items():
field_info = model.model_fields.get(field_name)
field_description = field_info.description if field_info else None

# Step 2: Process other types
example_data[field_name] = process_field_type(field_type, field_info)

for field_name, field_info in model.model_fields.items():
example_data[field_name] = process_field_type(
field_info.annotation, field_info, field_name, depth + 1
)
if field_info.description:
example_data.yaml_set_comment_before_after_key(
field_name, before=field_info.description, indent=depth * 2
)
if field_info.default != PydanticUndefined:
example_data.yaml_set_comment_before_after_key(
field_name, f"default value: {field_info.default}", indent=depth * 2
)
elif field_info.default_factory:
example_data.yaml_set_comment_before_after_key(
field_name,
f"default value: {field_info.default_factory()}",
indent=depth * 2,
)
example_data.yaml_set_comment_before_after_key(
field_name, f"required: {field_info.is_required()}", indent=depth * 2
)
return example_data


def process_field_type(field_type: Any, field_info) -> Any:
def process_field_type(field_type: Any, field_info, field_path, depth) -> Any:
"""
Processes a field type and generates example data based on its type.
"""
Expand All @@ -31,24 +47,24 @@ def process_field_type(field_type: Any, field_info) -> Any:
annotated_type = get_args(field_type)[0]

# Process the unwrapped type
return process_field_type(annotated_type, field_info)
return process_field_type(annotated_type, field_info, field_path, depth)

# Handle Union
if get_origin(field_type) == Union:
union_types = get_args(field_type)

# Generate example for the first type in the Union
primary_type = union_types[0]
return process_field_type(primary_type, field_info)
return process_field_type(primary_type, field_info, field_path, depth)

# Handle Pydantic models
if isinstance(field_type, type) and issubclass(field_type, BaseModel):
return generate_model_example(field_type)
return generate_model_example(field_type, depth=depth)

# Handle lists
if get_origin(field_type) == list:
list_type = get_args(field_type)[0]
return [process_field_type(list_type, field_info)]
return [process_field_type(list_type, field_info, field_path, depth + 1)]

# Handle basic types
return generate_basic_example(field_type, field_info)
Expand Down Expand Up @@ -98,7 +114,8 @@ def run(self):
# Ensure the class has the config_model attribute
if not hasattr(step_class, "config_model"):
raise ValueError(
f"The step class '{step_class}' does not have a 'config_model' attribute."
f"The step class '{step_class}' does not "
"have a 'config_model' attribute."
)

config_model = step_class.config_model
Expand All @@ -119,20 +136,29 @@ def run(self):
enable_setting = getattr(step_class, "enable_setting", None)

# Generate the model example data
example_data = generate_model_example(config_model)
example_data = generate_model_example(config_model, depth=1)

data = {}
if enable_setting:
data[enable_setting] = True
if namespace:
data[namespace] = example_data

# Convert the example data to YAML format using safe_dump to ensure proper formatting
yaml_example = yaml.safe_dump(data, default_flow_style=False, sort_keys=False)
# Convert the example data to YAML format using safe_dump to
# ensure proper formatting
yaml = ruamel.yaml.YAML()
yaml.indent(mapping=2, sequence=4, offset=2)

import io

output = io.StringIO()
yaml.dump(data, output)
yaml_example = output.getvalue()

# Create a code block with YAML formatting
literal_block = nodes.literal_block(yaml_example, yaml_example)
literal_block["language"] = "yaml"

# Return the node to be inserted into the document
return [literal_block]

Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ requires-python = ">=3.10"
dependencies = [
"django>=3.2,<5.0",
"pydantic>=2",
"pydantic-settings[yaml]>=2.2"
"pydantic-settings[yaml]>=2.2",
"ruamel.yaml>=0.18.10",
]

[project.urls]
Expand Down

0 comments on commit b802dc5

Please sign in to comment.