Skip to content

Commit 705de4e

Browse files
author
SMP Docs Bot
committed
Deployed 20c261e to 7.0.0 with MkDocs 1.6.1 and mike 2.1.3
1 parent cc5502c commit 705de4e

68 files changed

Lines changed: 39453 additions & 3 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

7.0.0/404.html

Lines changed: 757 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
"""Iterate through smpclient/requests and add inherited docstrings.
2+
3+
This file iterates through all of the python files in the smpclient/requests
4+
directory and changes them. It will import the classes and check if they have
5+
a docstring or not. If they do not have a docstring, it will get the docstring
6+
from the parent class, add it to the class, and rewrite the file.
7+
8+
This is solely for generating documentation with mkdocs.
9+
10+
It is wrangled LLM code and should be replaced ASAP.
11+
"""
12+
13+
import ast
14+
import importlib.util
15+
import inspect
16+
import os
17+
from typing import Any
18+
19+
from pydantic import BaseModel
20+
21+
22+
class ClassInfo:
23+
def __init__(self, name: str, lineno: int, col_offset: int, original_text: str):
24+
self.name = name
25+
self.lineno = lineno
26+
self.col_offset = col_offset
27+
self.original_text = original_text
28+
self.docstring: str | None = None
29+
30+
def add_docstring(self, docstring: str) -> None:
31+
"""Add a docstring to the class."""
32+
indent = ' ' * (self.col_offset + 4)
33+
formatted_docstring = format_docstring(docstring, indent)
34+
self.docstring = formatted_docstring
35+
36+
def get_updated_text(self) -> str:
37+
"""Get the updated class text with the new docstring."""
38+
if self.docstring:
39+
lines = self.original_text.split('\n')
40+
lines.insert(1, self.docstring)
41+
return '\n'.join(lines)
42+
return self.original_text
43+
44+
45+
def format_docstring(docstring: str, indent: str) -> str:
46+
"""Format the docstring with the correct indentation."""
47+
lines = docstring.split('\n')
48+
indented_lines = [f'{indent}"""{lines[0]}\n']
49+
indented_lines += [f'{line}' for line in lines[1:]]
50+
indented_lines.append(f'{indent}"""')
51+
return '\n'.join(indented_lines)
52+
53+
54+
def get_docstring_from_parent(cls: type) -> str | None:
55+
"""Get the docstring from the parent class."""
56+
for base in cls.__bases__:
57+
if base.__doc__:
58+
return base.__doc__
59+
return None
60+
61+
62+
def get_field_docstring(cls: type[BaseModel], field_name: str) -> str:
63+
"""Get the docstring of a field from the class."""
64+
for name, obj in inspect.getmembers(cls):
65+
if name == field_name:
66+
return obj.__doc__ or "No docstring provided."
67+
return "No docstring found."
68+
69+
70+
def format_type(annotation: type[Any] | None) -> str:
71+
"""Format the type to show module and class name."""
72+
if annotation is None:
73+
raise ValueError("Annotation cannot be None")
74+
if hasattr(annotation, '__name__'): # Handles regular types like `int`, `str`, etc.
75+
# get the annotations like list[str] for example
76+
if hasattr(annotation, '__args__'):
77+
return f"{annotation.__name__}[{format_type(annotation.__args__[0])}]"
78+
return f"{annotation.__name__}"
79+
elif hasattr(annotation, '__origin__'): # Handles generic types like list[str], Optional[int]
80+
return f"{annotation.__origin__.__module__}.{annotation.__origin__.__name__}"
81+
return str(annotation) # Fallback for other types
82+
83+
84+
def get_pydantic_fields(cls: type[BaseModel]) -> str:
85+
"""Get the fields of a Pydantic model and format them as Google-style Args."""
86+
if not issubclass(cls, BaseModel):
87+
return ""
88+
89+
fields = cls.model_fields
90+
args = "\n Args:\n"
91+
for field_name, field_info in fields.items():
92+
if field_name in ("header, version, sequence, smp_data"):
93+
continue
94+
field_type = format_type(field_info.annotation)
95+
96+
# split the field_info.description by newlines and join them with a newline
97+
# and 12 spaces, removing blank lines
98+
description = (
99+
"\n ".join(
100+
filter(lambda x: x.strip() != "", field_info.description.split("\n"))
101+
)
102+
if field_info.description
103+
else ""
104+
)
105+
106+
args += f" {field_name} ({field_type}): {description}\n"
107+
if args.endswith("Args:\n"):
108+
return ""
109+
return args
110+
111+
112+
def parse_file(file_path: str) -> list[ClassInfo]:
113+
"""Parse the file and extract class definitions."""
114+
with open(file_path) as file:
115+
lines = file.readlines()
116+
tree = ast.parse(''.join(lines))
117+
118+
classes = []
119+
for node in ast.walk(tree):
120+
if isinstance(node, ast.ClassDef):
121+
class_name = node.name
122+
lineno = node.lineno - 1
123+
col_offset = node.col_offset
124+
class_body = lines[lineno : lineno + len(node.body) + 1]
125+
original_text = ''.join(class_body)
126+
classes.append(ClassInfo(class_name, lineno, col_offset, original_text))
127+
return classes
128+
129+
130+
def update_class_docstrings(file_path: str) -> None:
131+
"""Update class docstrings in a given file."""
132+
classes = parse_file(file_path)
133+
module_name = file_path.replace('/', '.').replace('.py', '')
134+
spec = importlib.util.spec_from_file_location(module_name, file_path)
135+
if spec is None:
136+
raise ValueError(f"Could not find spec for {module_name}")
137+
module = importlib.util.module_from_spec(spec)
138+
if spec.loader is None:
139+
raise ValueError(f"Could not find loader for {module_name}")
140+
spec.loader.exec_module(module)
141+
142+
for class_info in classes:
143+
cls = getattr(module, class_info.name)
144+
if not cls.__doc__:
145+
parent_docstring = get_docstring_from_parent(cls)
146+
if parent_docstring:
147+
args_section = get_pydantic_fields(cls)
148+
full_docstring = parent_docstring + args_section
149+
class_info.add_docstring(full_docstring)
150+
151+
with open(file_path, encoding="utf-8") as file:
152+
lines = file.readlines()
153+
154+
updated_lines = []
155+
class_index = 0
156+
for i, line in enumerate(lines):
157+
if class_index < len(classes) and i == classes[class_index].lineno:
158+
updated_lines.append(classes[class_index].get_updated_text())
159+
class_index += 1
160+
else:
161+
updated_lines.append(line)
162+
163+
with open(file_path, 'w', encoding="utf-8") as file:
164+
file.writelines(updated_lines)
165+
166+
167+
def main() -> None:
168+
directory = 'smpclient/requests'
169+
for root, _, files in os.walk(directory):
170+
for file in files:
171+
if file.endswith('.py'):
172+
file_path = os.path.join(root, file)
173+
update_class_docstrings(file_path)
174+
175+
176+
if __name__ == '__main__':
177+
main()

0 commit comments

Comments
 (0)