-
Notifications
You must be signed in to change notification settings - Fork 309
/
Copy pathgenerate_docs.py
329 lines (263 loc) · 11.9 KB
/
generate_docs.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
# Copyright (c) 2025 Stephen G. Pope
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import os
import sys
import json
import requests
import time
from pathlib import Path
from datetime import datetime, timedelta
def load_config():
"""Load configuration from env_shell.json file."""
config_path = Path(__file__).parent / '.env_shell.json'
try:
with open(config_path, 'r') as f:
config = json.load(f)
return config.get('ANTHROPIC_API_KEY'), config.get('API_DOC_OUTPUT_DIR')
except FileNotFoundError:
print(f"Error: Configuration file not found at: {config_path}")
sys.exit(1)
except json.JSONDecodeError:
print(f"Error: Invalid JSON in configuration file: {config_path}")
sys.exit(1)
except Exception as e:
print(f"Error loading configuration: {str(e)}")
sys.exit(1)
def load_app_context():
"""Load the app.py file from the root of the repository."""
try:
# Get the root directory by going up from the current file's location
root_dir = Path(__file__).parent.parent
app_path = Path(__file__).parent / 'app.py'
if not app_path.exists():
print("Warning: app.py not found in repository root. Documentation will be generated without API context.")
return None
with open(app_path, 'r', encoding='utf-8') as f:
return f.read()
except Exception as e:
print(f"Warning: Could not load app.py: {str(e)}")
return None
# The prompt template to send to Claude
CLAUDE_PROMPT = '''
I am providing you with a Python file containing API endpoint definitions.
First, here is the main application context from app.py that shows how the API is structured and handled:
** app.py below
{app_context}
** app.py DONE
Now, please read through the following endpoint code and analyze it in the context of the main application:
**endpoint below
{file_content}
Please generate detailed documentation in Markdown format as follows:
1. Overview: Describe the purpose of the endpoint and how it fits into the overall API structure shown in app.py.
2. Endpoint: Specify the URL path and HTTP method.
3. Request:
- Headers: List any required headers, such as the x-api-key headers.
- Body Parameters: List the required and optional parameters, including the parameter type and purpose.
- Specifically study the validate_payload directive in the routes file to build the documentation
- Example Request: Provide a sample request payload and a sample curl command.
4. Response:
- Success Response: Reference the endpoint and general response from the app.py to show a full sample response from the api
- Error Responses: Include examples of common error status codes, with example JSON responses for each.
5. Error Handling:
- Describe common errors, like missing or invalid parameters, and indicate which status codes they produce
- Include any specific error handling from the main application context
6. Usage Notes: Any additional notes on using the endpoint effectively.
7. Common Issues: List any common issues a user might encounter.
8. Best Practices: Any recommended best practices for this endpoint.
Format the documentation with markdown headings, bullet points, and code blocks.
'''
def call_claude_api(message: str, api_key: str) -> str:
"""Make a direct API call to Claude."""
headers = {
"Content-Type": "application/json",
"x-api-key": api_key,
"anthropic-version": "2023-06-01"
}
data = {
"model": "claude-3-sonnet-20240229",
"max_tokens": 4096,
"temperature": 0,
"messages": [
{"role": "user", "content": message}
]
}
response = requests.post(
"https://api.anthropic.com/v1/messages",
headers=headers,
json=data
)
if response.status_code != 200:
raise Exception(f"API call failed with status {response.status_code}: {response.text}")
return response.json()["content"][0]["text"]
def should_skip_doc_generation(output_file: Path, force: bool = False) -> bool:
"""
Check if documentation was updated in the last 24 hours.
Args:
output_file: Path to the output markdown file
force: If True, always return False to force generation
Returns:
bool: True if file was updated in the last 24 hours and not forced, False otherwise
"""
# If force flag is provided, never skip
if force:
return False
if not output_file.exists():
return False
# Get file modification time
mod_time = datetime.fromtimestamp(output_file.stat().st_mtime)
# Check if file was modified in the last 24 hours
time_threshold = datetime.now() - timedelta(hours=24)
return mod_time > time_threshold
def process_single_file(source_file: Path, output_path: Path, api_key: str, force: bool = False):
"""
Process a single Python file.
Args:
source_file: Path to the source Python file
output_path: Path to output the markdown file
api_key: Anthropic API key
force: If True, generate docs even if they were updated recently
"""
try:
# Create output file path
if output_path.is_dir():
output_file = output_path / source_file.with_suffix('.md').name
else:
output_file = output_path
# Check if docs were recently updated
if should_skip_doc_generation(output_file, force):
print(f"Skipping {source_file} - documentation updated within the last 24 hours")
return
# Read the source file
with open(source_file, 'r', encoding='utf-8') as f:
file_content = f.read()
# Load app.py context
app_context = load_app_context()
if app_context is None:
app_context = "No app.py context available."
# Create the full prompt
message = CLAUDE_PROMPT.format(
app_context=app_context,
file_content=file_content
)
# Get documentation from Claude
markdown_content = call_claude_api(message, api_key)
# Create necessary directories
output_file.parent.mkdir(parents=True, exist_ok=True)
# Write the markdown content (will overwrite if exists)
with open(output_file, 'w', encoding='utf-8') as f:
f.write(markdown_content)
print(f"Generated documentation for: {source_file}")
print(f"Output saved to: {output_file}")
except Exception as e:
print(f"Error processing {source_file}: {str(e)}", file=sys.stderr)
def process_directory(source_dir: Path, output_dir: Path, api_key: str, force: bool = False):
"""Process all Python files in the source directory recursively."""
# Track statistics
total_files = 0
processed_files = 0
skipped_files = 0
error_files = 0
start_time = time.time()
# Walk through all files in source directory
for root, _, files in os.walk(source_dir):
for file in files:
if file.endswith('.py'):
# Get the source file path
source_file = Path(root) / file
total_files += 1
try:
# Calculate relative path to maintain directory structure
rel_path = source_file.relative_to(source_dir)
output_file = output_dir / rel_path.with_suffix('.md')
# Create necessary directories
output_file.parent.mkdir(parents=True, exist_ok=True)
# Check if we should skip this file
if should_skip_doc_generation(output_file, force):
print(f"Skipping {source_file} - documentation updated within the last 24 hours")
skipped_files += 1
continue
# Process the file
process_single_file(source_file, output_file, api_key, force)
processed_files += 1
except Exception as e:
print(f"Error processing {source_file}: {str(e)}", file=sys.stderr)
error_files += 1
# Print summary
elapsed_time = time.time() - start_time
print("\nDocumentation Generation Summary:")
print(f"Total Python files found: {total_files}")
print(f"Files processed: {processed_files}")
print(f"Files skipped (updated in last 24h): {skipped_files}")
print(f"Files with errors: {error_files}")
print(f"Total time: {elapsed_time:.2f} seconds")
def main():
# Check if --force flag is provided
force_generation = False
source_path_arg = None
for arg in sys.argv[1:]:
if arg == "--force":
force_generation = True
else:
source_path_arg = arg
if not source_path_arg:
print("Usage: python script.py <source_path> [--force]")
print("Note: source_path can be either a single .py file or a directory")
print("Options:")
print(" --force: Generate documentation even if it was updated within 24 hours")
print("\nPlease ensure .env_shell.json exists in the same directory with:")
print(" ANTHROPIC_API_KEY: Your Anthropic API key")
print(" API_DOC_OUTPUT_DIR: Directory where documentation will be saved")
sys.exit(1)
# Load configuration from JSON file
api_key, output_dir = load_config()
# Validate configuration
if not api_key:
print("Error: ANTHROPIC_API_KEY not found in configuration file")
sys.exit(1)
if not output_dir:
print("Error: API_DOC_OUTPUT_DIR not found in configuration file")
sys.exit(1)
output_path = Path(output_dir)
# Get and validate source path
source_path = Path(source_path_arg)
if not source_path.exists():
print(f"Error: Source path does not exist: {source_path}")
sys.exit(1)
if source_path.is_file() and not source_path.suffix == '.py':
print("Error: Source file must be a Python file (.py)")
sys.exit(1)
# Create output directory if it doesn't exist
output_path.mkdir(parents=True, exist_ok=True)
print(f"Starting documentation generation...")
print(f"Source: {source_path}")
print(f"Output: {output_path}")
if force_generation:
print(f"Force flag enabled: Will generate all documentation regardless of last update time.\n")
else:
print(f"Note: Files updated within the last 24 hours will be skipped (use --force to override).\n")
# Process based on source type
if source_path.is_file():
# For a single file
output_file = output_path / source_path.with_suffix('.md').name if output_path.is_dir() else output_path
# Check if should skip
if should_skip_doc_generation(output_file, force_generation):
print(f"Skipping {source_path} - documentation updated within the last 24 hours")
else:
process_single_file(source_path, output_path, api_key, force_generation)
else:
process_directory(source_path, output_path, api_key, force_generation)
if __name__ == "__main__":
main()