forked from modal-labs/modal-client
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgen_reference_docs.py
More file actions
188 lines (160 loc) · 6.27 KB
/
Copy pathgen_reference_docs.py
File metadata and controls
188 lines (160 loc) · 6.27 KB
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
# Copyright Modal Labs 2023
import importlib
import inspect
import json
import os
import sys
import warnings
from typing import NamedTuple
from synchronicity.synchronizer import FunctionWithAio
from .mdmd.mdmd import (
Category,
class_str,
default_filter,
function_str,
module_items,
module_str,
object_is_private,
package_filter,
)
class DocItem(NamedTuple):
label: str
category: Category
document: str
in_sidebar: bool = True
def validate_doc_item(docitem: DocItem) -> DocItem:
# Check that unwanted strings aren't leaking into our docs.
bad_strings = [
# Presence of a to-do inside a `DocItem` usually indicates it's been
# placed inside a function signature definition or right underneath it, before the body.
# Fix by moving the to-do into the body or above the signature.
"TODO:"
]
for line in docitem.document.splitlines():
for bad_str in bad_strings:
if bad_str in line:
msg = f"Found unwanted string '{bad_str}' in content for item '{docitem.label}'. Problem line: {line}"
raise ValueError(msg)
return docitem
def run(output_dir: str = None):
"""Generate Modal docs."""
import modal
ordered_doc_items: list[DocItem] = []
documented_items = set()
def filter_non_aio(module, name):
return not name.lower().startswith("aio")
def filter_already_documented(module, name):
item = getattr(module, name)
try:
if item in documented_items:
return False
except TypeError: # unhashable stuff
print(f"Warning: could not document item {name}: {item}:")
return False
documented_items.add(item)
return True
def modal_default_filter(module, name):
return default_filter(module, name) and filter_non_aio(module, name) and filter_already_documented(module, name)
def top_level_filter(module, name):
item = getattr(module, name)
if object_is_private(name, item) or inspect.ismodule(item):
return False
return package_filter("modal") and filter_already_documented(module, name) and filter_non_aio(module, name)
base_title_level = "#"
forced_module_docs = [
("modal.billing", "modal.billing"),
("modal.call_graph", "modal.call_graph"),
("modal.container_process", "modal.container_process"),
("modal.gpu", "modal.gpu"),
("modal.io_streams", "modal.io_streams"),
("modal.file_io", "modal.file_io"),
]
# These aren't defined in `modal`, but should still be documented as top-level entries.
forced_members: set[str] = set()
# These are excluded from the sidebar, typically to 'soft release' some documentation.
sidebar_excluded: set[str] = {"modal.NetworkFileSystem"}
for title, modulepath in forced_module_docs:
module = importlib.import_module(modulepath)
document = module_str(modulepath, module, title_level=base_title_level, filter_items=modal_default_filter)
if document:
ordered_doc_items.append(
validate_doc_item(
DocItem(
label=title,
category=Category.MODULE,
document=document,
in_sidebar=title not in sidebar_excluded,
)
)
)
def f(module, member_name):
return top_level_filter(module, member_name) or (member_name in forced_members)
# now add all remaining top level modal.X entries
for qual_name, item_name, item in module_items(modal, filter_items=f):
if object_is_private(item_name, item):
continue # skip stuff that's part of explicit `handle_objects` above
title = f"modal.{item_name}"
if inspect.isclass(item):
content = f"{base_title_level} {qual_name}\n\n" + class_str(item_name, item, base_title_level)
category = Category.CLASS
elif inspect.isroutine(item) or isinstance(item, FunctionWithAio):
content = f"{base_title_level} {qual_name}\n\n" + function_str(item_name, item)
category = Category.FUNCTION
elif inspect.ismodule(item):
continue # skipping imported modules
else:
warnings.warn(f"Not sure how to document: {item_name} ({item})")
continue
ordered_doc_items.append(
validate_doc_item(
DocItem(
label=title,
category=category,
document=content,
in_sidebar=title not in sidebar_excluded,
)
)
)
ordered_doc_items.sort()
for modulepath in ["modal.exception", "modal.config"]:
module = importlib.import_module(modulepath)
document = module_str(modulepath, module, title_level=base_title_level, filter_items=modal_default_filter)
ordered_doc_items.append(
DocItem(
label=modulepath,
category=Category.MODULE,
document=document,
)
)
# TODO: add some way of documenting our .aio sub-methods
make_markdown_docs(
ordered_doc_items,
output_dir,
)
def make_markdown_docs(items: list[DocItem], output_dir: str = None):
def _write_file(rel_path: str, data: str):
if output_dir is None:
print(f"<<< {rel_path}")
print(data)
print(f">>> {rel_path}")
return
filename = os.path.join(output_dir, rel_path)
print("Writing to", filename)
os.makedirs(os.path.dirname(filename), exist_ok=True)
with open(filename, "w") as fp:
fp.write(data)
sidebar_items = []
for item in items:
if item.in_sidebar:
sidebar_items.append(
{
"label": item.label,
"category": item.category.value,
}
)
_write_file(f"{item.label}.md", item.document)
sidebar_data = {"items": sidebar_items}
_write_file("sidebar.json", json.dumps(sidebar_data))
if __name__ == "__main__":
# running this module outputs docs to stdout for inspection, useful for debugging
run(None if len(sys.argv) <= 1 else sys.argv[1])