drf-to-mkdoc 0.1.5__py3-none-any.whl → 1.0.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of drf-to-mkdoc might be problematic. Click here for more details.
- drf_to_mkdoc/__init__.py +1 -1
- drf_to_mkdoc/apps.py +6 -2
- drf_to_mkdoc/conf/defaults.py +0 -1
- drf_to_mkdoc/conf/settings.py +8 -5
- drf_to_mkdoc/management/commands/build_docs.py +61 -20
- drf_to_mkdoc/management/commands/generate_docs.py +1 -2
- drf_to_mkdoc/management/commands/generate_model_docs.py +37 -7
- drf_to_mkdoc/static/drf-to-mkdoc/javascripts/endpoints-filter.js +189 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/accessibility.css +21 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/animations.css +11 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/badges.css +54 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/base.css +15 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/endpoint-content.css +48 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/endpoints-grid.css +75 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/filter-section.css +209 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/fixes.css +44 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/layout.css +31 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/loading.css +35 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/responsive.css +89 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/sections.css +35 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/stats.css +34 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/tags.css +92 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/theme-toggle.css +30 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/variables.css +30 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/extra.css +358 -0
- drf_to_mkdoc/utils/common.py +31 -26
- drf_to_mkdoc/utils/endpoint_generator.py +130 -131
- drf_to_mkdoc/utils/extractors/query_parameter_extractors.py +7 -7
- {drf_to_mkdoc-0.1.5.dist-info → drf_to_mkdoc-1.0.7.dist-info}/METADATA +1 -1
- drf_to_mkdoc-1.0.7.dist-info/RECORD +43 -0
- drf_to_mkdoc-0.1.5.dist-info/RECORD +0 -25
- {drf_to_mkdoc-0.1.5.dist-info → drf_to_mkdoc-1.0.7.dist-info}/WHEEL +0 -0
- {drf_to_mkdoc-0.1.5.dist-info → drf_to_mkdoc-1.0.7.dist-info}/licenses/LICENSE +0 -0
- {drf_to_mkdoc-0.1.5.dist-info → drf_to_mkdoc-1.0.7.dist-info}/top_level.txt +0 -0
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
1
|
import ast
|
|
4
2
|
import inspect
|
|
5
3
|
import json
|
|
4
|
+
import logging
|
|
6
5
|
from collections import defaultdict
|
|
7
6
|
from pathlib import Path
|
|
8
7
|
from typing import Any
|
|
9
8
|
|
|
9
|
+
from django.apps import apps
|
|
10
|
+
from rest_framework import serializers
|
|
11
|
+
|
|
10
12
|
from drf_to_mkdoc.conf.settings import drf_to_mkdoc_settings
|
|
11
13
|
from drf_to_mkdoc.utils.common import (
|
|
12
14
|
create_safe_filename,
|
|
@@ -17,30 +19,35 @@ from drf_to_mkdoc.utils.common import (
|
|
|
17
19
|
get_custom_schema,
|
|
18
20
|
write_file,
|
|
19
21
|
)
|
|
20
|
-
from drf_to_mkdoc.utils.extractors.query_parameter_extractors import
|
|
21
|
-
|
|
22
|
+
from drf_to_mkdoc.utils.extractors.query_parameter_extractors import (
|
|
23
|
+
extract_query_parameters_from_view,
|
|
24
|
+
)
|
|
25
|
+
from drf_to_mkdoc.utils.md_generators.query_parameters_generators import (
|
|
26
|
+
generate_query_parameters_md,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger()
|
|
22
30
|
|
|
23
31
|
|
|
24
32
|
def analyze_serializer_method_field_schema(serializer_class, field_name: str) -> dict:
|
|
25
33
|
"""Analyze a SerializerMethodField to determine its actual return type schema."""
|
|
26
34
|
method_name = f"get_{field_name}"
|
|
27
35
|
|
|
28
|
-
|
|
29
36
|
# Strategy 2: Check type annotations
|
|
30
37
|
schema_from_annotations = _extract_schema_from_type_hints(serializer_class, method_name)
|
|
31
38
|
if schema_from_annotations:
|
|
32
39
|
return schema_from_annotations
|
|
33
|
-
|
|
40
|
+
|
|
34
41
|
# Strategy 3: Analyze method source code
|
|
35
42
|
schema_from_source = _analyze_method_source_code(serializer_class, method_name)
|
|
36
43
|
if schema_from_source:
|
|
37
44
|
return schema_from_source
|
|
38
|
-
|
|
45
|
+
|
|
39
46
|
# Strategy 4: Runtime analysis (sample execution)
|
|
40
47
|
schema_from_runtime = _analyze_method_runtime(serializer_class, method_name)
|
|
41
48
|
if schema_from_runtime:
|
|
42
49
|
return schema_from_runtime
|
|
43
|
-
|
|
50
|
+
|
|
44
51
|
# Fallback to string
|
|
45
52
|
return {"type": "string"}
|
|
46
53
|
|
|
@@ -51,24 +58,24 @@ def _extract_schema_from_decorator(serializer_class, method_name: str) -> dict:
|
|
|
51
58
|
method = getattr(serializer_class, method_name, None)
|
|
52
59
|
if not method:
|
|
53
60
|
return None
|
|
54
|
-
|
|
61
|
+
|
|
55
62
|
# Check if method has the decorator attribute (drf-spectacular)
|
|
56
|
-
if hasattr(method,
|
|
63
|
+
if hasattr(method, "_spectacular_annotation"):
|
|
57
64
|
annotation = method._spectacular_annotation
|
|
58
65
|
# Handle OpenApiTypes
|
|
59
|
-
if hasattr(annotation,
|
|
66
|
+
if hasattr(annotation, "type"):
|
|
60
67
|
return {"type": annotation.type}
|
|
61
|
-
|
|
68
|
+
if isinstance(annotation, dict):
|
|
62
69
|
return annotation
|
|
63
|
-
|
|
70
|
+
|
|
64
71
|
# Check for drf-yasg decorator
|
|
65
|
-
if hasattr(method,
|
|
72
|
+
if hasattr(method, "_swagger_serializer_method"):
|
|
66
73
|
swagger_info = method._swagger_serializer_method
|
|
67
|
-
if hasattr(swagger_info,
|
|
74
|
+
if hasattr(swagger_info, "many") and hasattr(swagger_info, "child"):
|
|
68
75
|
return {"type": "array", "items": {"type": "object"}}
|
|
69
|
-
|
|
76
|
+
|
|
70
77
|
except Exception:
|
|
71
|
-
|
|
78
|
+
logger.exception("Failed to extract schema from decorator")
|
|
72
79
|
return None
|
|
73
80
|
|
|
74
81
|
|
|
@@ -78,30 +85,30 @@ def _extract_schema_from_type_hints(serializer_class, method_name: str) -> dict:
|
|
|
78
85
|
method = getattr(serializer_class, method_name, None)
|
|
79
86
|
if not method:
|
|
80
87
|
return None
|
|
81
|
-
|
|
88
|
+
|
|
82
89
|
signature = inspect.signature(method)
|
|
83
90
|
return_annotation = signature.return_annotation
|
|
84
|
-
|
|
91
|
+
|
|
85
92
|
if return_annotation and return_annotation != inspect.Signature.empty:
|
|
86
93
|
# Handle common type hints
|
|
87
|
-
if return_annotation
|
|
88
|
-
return {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
if return_annotation in (int, str, bool, float):
|
|
95
|
+
return {
|
|
96
|
+
int: {"type": "integer"},
|
|
97
|
+
str: {"type": "string"},
|
|
98
|
+
bool: {"type": "boolean"},
|
|
99
|
+
float: {"type": "number"},
|
|
100
|
+
}[return_annotation]
|
|
101
|
+
|
|
102
|
+
if hasattr(return_annotation, "__origin__"):
|
|
96
103
|
# Handle generic types like List[str], Dict[str, Any]
|
|
97
104
|
origin = return_annotation.__origin__
|
|
98
105
|
if origin is list:
|
|
99
106
|
return {"type": "array", "items": {"type": "string"}}
|
|
100
|
-
|
|
107
|
+
if origin is dict:
|
|
101
108
|
return {"type": "object"}
|
|
102
|
-
|
|
109
|
+
|
|
103
110
|
except Exception:
|
|
104
|
-
|
|
111
|
+
logger.exception("Failed to extract schema from type hints")
|
|
105
112
|
return None
|
|
106
113
|
|
|
107
114
|
|
|
@@ -111,18 +118,18 @@ def _analyze_method_source_code(serializer_class, method_name: str) -> dict:
|
|
|
111
118
|
method = getattr(serializer_class, method_name, None)
|
|
112
119
|
if not method:
|
|
113
120
|
return None
|
|
114
|
-
|
|
121
|
+
|
|
115
122
|
source = inspect.getsource(method)
|
|
116
123
|
tree = ast.parse(source)
|
|
117
|
-
|
|
124
|
+
|
|
118
125
|
# Find return statements and analyze them
|
|
119
126
|
return_analyzer = ReturnStatementAnalyzer()
|
|
120
127
|
return_analyzer.visit(tree)
|
|
121
|
-
|
|
128
|
+
|
|
122
129
|
return _infer_schema_from_return_patterns(return_analyzer.return_patterns)
|
|
123
|
-
|
|
130
|
+
|
|
124
131
|
except Exception:
|
|
125
|
-
|
|
132
|
+
logger.exception("Failed to analyze method source code")
|
|
126
133
|
return None
|
|
127
134
|
|
|
128
135
|
|
|
@@ -130,32 +137,39 @@ def _analyze_method_runtime(serializer_class, method_name: str) -> dict:
|
|
|
130
137
|
"""Analyze method by creating mock instances and examining return values."""
|
|
131
138
|
try:
|
|
132
139
|
# Create a basic mock object with common attributes
|
|
133
|
-
mock_obj = type(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
140
|
+
mock_obj = type(
|
|
141
|
+
"MockObj",
|
|
142
|
+
(),
|
|
143
|
+
{
|
|
144
|
+
"id": 1,
|
|
145
|
+
"pk": 1,
|
|
146
|
+
"name": "test",
|
|
147
|
+
"count": lambda: 5,
|
|
148
|
+
"items": type("items", (), {"count": lambda: 3, "all": lambda: []})(),
|
|
149
|
+
},
|
|
150
|
+
)()
|
|
151
|
+
|
|
138
152
|
serializer_instance = serializer_class()
|
|
139
153
|
method = getattr(serializer_instance, method_name, None)
|
|
140
|
-
|
|
154
|
+
|
|
141
155
|
if not method:
|
|
142
156
|
return None
|
|
143
|
-
|
|
157
|
+
|
|
144
158
|
# Execute method with mock data
|
|
145
159
|
result = method(mock_obj)
|
|
146
160
|
return _infer_schema_from_value(result)
|
|
147
|
-
|
|
161
|
+
|
|
148
162
|
except Exception:
|
|
149
|
-
|
|
163
|
+
logger.exception("Failed to analyse method runtime")
|
|
150
164
|
return None
|
|
151
165
|
|
|
152
166
|
|
|
153
167
|
class ReturnStatementAnalyzer(ast.NodeVisitor):
|
|
154
168
|
"""AST visitor to analyze return statements in method source code."""
|
|
155
|
-
|
|
169
|
+
|
|
156
170
|
def __init__(self):
|
|
157
171
|
self.return_patterns = []
|
|
158
|
-
|
|
172
|
+
|
|
159
173
|
def visit_Return(self, node):
|
|
160
174
|
"""Visit return statements and extract patterns."""
|
|
161
175
|
if node.value:
|
|
@@ -163,92 +177,86 @@ class ReturnStatementAnalyzer(ast.NodeVisitor):
|
|
|
163
177
|
if pattern:
|
|
164
178
|
self.return_patterns.append(pattern)
|
|
165
179
|
self.generic_visit(node)
|
|
166
|
-
|
|
180
|
+
|
|
167
181
|
def _analyze_return_value(self, node) -> dict:
|
|
168
182
|
"""Analyze different types of return value patterns."""
|
|
169
183
|
if isinstance(node, ast.Dict):
|
|
170
184
|
return self._analyze_dict_return(node)
|
|
171
|
-
|
|
185
|
+
if isinstance(node, ast.List):
|
|
172
186
|
return self._analyze_list_return(node)
|
|
173
|
-
|
|
187
|
+
if isinstance(node, ast.Constant):
|
|
174
188
|
return self._analyze_constant_return(node)
|
|
175
|
-
|
|
189
|
+
if isinstance(node, ast.Call):
|
|
176
190
|
return self._analyze_method_call_return(node)
|
|
177
|
-
|
|
191
|
+
if isinstance(node, ast.Attribute):
|
|
178
192
|
return self._analyze_attribute_return(node)
|
|
179
193
|
return None
|
|
180
|
-
|
|
194
|
+
|
|
181
195
|
def _analyze_dict_return(self, node) -> dict:
|
|
182
196
|
"""Analyze dictionary return patterns."""
|
|
183
197
|
properties = {}
|
|
184
|
-
for key, value in zip(node.keys, node.values):
|
|
198
|
+
for key, value in zip(node.keys, node.values, strict=False):
|
|
185
199
|
if isinstance(key, ast.Constant) and isinstance(key.value, str):
|
|
186
200
|
prop_schema = self._infer_value_type(value)
|
|
187
201
|
if prop_schema:
|
|
188
202
|
properties[key.value] = prop_schema
|
|
189
|
-
|
|
190
|
-
return {
|
|
191
|
-
|
|
192
|
-
"properties": properties
|
|
193
|
-
}
|
|
194
|
-
|
|
203
|
+
|
|
204
|
+
return {"type": "object", "properties": properties}
|
|
205
|
+
|
|
195
206
|
def _analyze_list_return(self, node) -> dict:
|
|
196
207
|
"""Analyze list return patterns."""
|
|
197
208
|
if node.elts:
|
|
198
209
|
# Analyze first element to determine array item type
|
|
199
210
|
first_element_schema = self._infer_value_type(node.elts[0])
|
|
200
|
-
return {
|
|
201
|
-
"type": "array",
|
|
202
|
-
"items": first_element_schema or {"type": "string"}
|
|
203
|
-
}
|
|
211
|
+
return {"type": "array", "items": first_element_schema or {"type": "string"}}
|
|
204
212
|
return {"type": "array", "items": {"type": "string"}}
|
|
205
|
-
|
|
213
|
+
|
|
206
214
|
def _analyze_constant_return(self, node) -> dict:
|
|
207
215
|
"""Analyze constant return values."""
|
|
208
216
|
return self._python_type_to_schema(type(node.value))
|
|
209
|
-
|
|
217
|
+
|
|
210
218
|
def _analyze_method_call_return(self, node) -> dict:
|
|
211
219
|
"""Analyze method call returns (like obj.count(), obj.items.all())."""
|
|
212
220
|
if isinstance(node.func, ast.Attribute):
|
|
213
221
|
method_name = node.func.attr
|
|
214
|
-
|
|
222
|
+
|
|
215
223
|
# Common Django ORM patterns
|
|
216
|
-
if method_name in [
|
|
224
|
+
if method_name in ["count"]:
|
|
217
225
|
return {"type": "integer"}
|
|
218
|
-
|
|
226
|
+
if method_name in ["all", "filter", "exclude"]:
|
|
219
227
|
return {"type": "array", "items": {"type": "object"}}
|
|
220
|
-
|
|
228
|
+
if method_name in ["first", "last", "get"]:
|
|
221
229
|
return {"type": "object"}
|
|
222
|
-
|
|
230
|
+
if method_name in ["exists"]:
|
|
223
231
|
return {"type": "boolean"}
|
|
224
|
-
|
|
232
|
+
|
|
225
233
|
return None
|
|
226
|
-
|
|
234
|
+
|
|
227
235
|
def _analyze_attribute_return(self, node) -> dict:
|
|
228
236
|
"""Analyze attribute access returns (like obj.name, obj.id)."""
|
|
229
237
|
if isinstance(node, ast.Attribute):
|
|
230
238
|
attr_name = node.attr
|
|
231
|
-
|
|
239
|
+
|
|
232
240
|
# Common field name patterns
|
|
233
|
-
if attr_name in [
|
|
241
|
+
if attr_name in ["id", "pk", "count"]:
|
|
234
242
|
return {"type": "integer"}
|
|
235
|
-
|
|
243
|
+
if attr_name in ["name", "title", "description", "slug"]:
|
|
236
244
|
return {"type": "string"}
|
|
237
|
-
|
|
245
|
+
if attr_name in ["is_active", "is_published", "enabled"]:
|
|
238
246
|
return {"type": "boolean"}
|
|
239
|
-
|
|
247
|
+
|
|
240
248
|
return None
|
|
241
|
-
|
|
249
|
+
|
|
242
250
|
def _infer_value_type(self, node) -> dict:
|
|
243
251
|
"""Infer schema type from AST node."""
|
|
244
252
|
if isinstance(node, ast.Constant):
|
|
245
253
|
return self._python_type_to_schema(type(node.value))
|
|
246
|
-
|
|
254
|
+
if isinstance(node, ast.Call):
|
|
247
255
|
return self._analyze_method_call_return(node)
|
|
248
|
-
|
|
256
|
+
if isinstance(node, ast.Attribute):
|
|
249
257
|
return self._analyze_attribute_return(node)
|
|
250
258
|
return {"type": "string"} # Default fallback
|
|
251
|
-
|
|
259
|
+
|
|
252
260
|
def _python_type_to_schema(self, python_type) -> dict:
|
|
253
261
|
"""Convert Python type to OpenAPI schema."""
|
|
254
262
|
type_mapping = {
|
|
@@ -266,20 +274,17 @@ def _infer_schema_from_return_patterns(patterns: list) -> dict:
|
|
|
266
274
|
"""Infer final schema from collected return patterns."""
|
|
267
275
|
if not patterns:
|
|
268
276
|
return None
|
|
269
|
-
|
|
277
|
+
|
|
270
278
|
# If all patterns are the same type, use that
|
|
271
|
-
if
|
|
279
|
+
if all(p.get("type") == patterns[0].get("type") for p in patterns):
|
|
272
280
|
# Merge object properties if multiple object returns
|
|
273
281
|
if patterns[0]["type"] == "object":
|
|
274
282
|
merged_properties = {}
|
|
275
283
|
for pattern in patterns:
|
|
276
284
|
merged_properties.update(pattern.get("properties", {}))
|
|
277
|
-
return {
|
|
278
|
-
"type": "object",
|
|
279
|
-
"properties": merged_properties
|
|
280
|
-
}
|
|
285
|
+
return {"type": "object", "properties": merged_properties}
|
|
281
286
|
return patterns[0]
|
|
282
|
-
|
|
287
|
+
|
|
283
288
|
# Mixed types - could be union, but default to string for OpenAPI compatibility
|
|
284
289
|
return {"type": "string"}
|
|
285
290
|
|
|
@@ -290,55 +295,47 @@ def _infer_schema_from_value(value: Any) -> dict:
|
|
|
290
295
|
properties = {}
|
|
291
296
|
for key, val in value.items():
|
|
292
297
|
properties[str(key)] = _infer_schema_from_value(val)
|
|
293
|
-
return {
|
|
294
|
-
|
|
295
|
-
"properties": properties
|
|
296
|
-
}
|
|
297
|
-
elif isinstance(value, list):
|
|
298
|
+
return {"type": "object", "properties": properties}
|
|
299
|
+
if isinstance(value, list):
|
|
298
300
|
if value:
|
|
299
|
-
return {
|
|
300
|
-
"type": "array",
|
|
301
|
-
"items": _infer_schema_from_value(value[0])
|
|
302
|
-
}
|
|
301
|
+
return {"type": "array", "items": _infer_schema_from_value(value[0])}
|
|
303
302
|
return {"type": "array", "items": {"type": "string"}}
|
|
304
|
-
|
|
303
|
+
if type(value) in (int, float, str, bool):
|
|
305
304
|
return {
|
|
306
305
|
int: {"type": "integer"},
|
|
307
306
|
float: {"type": "number"},
|
|
308
307
|
str: {"type": "string"},
|
|
309
|
-
bool: {"type": "boolean"}
|
|
308
|
+
bool: {"type": "boolean"},
|
|
310
309
|
}[type(value)]
|
|
311
|
-
|
|
312
|
-
return {"type": "string"}
|
|
310
|
+
return {"type": "string"}
|
|
313
311
|
|
|
314
312
|
|
|
315
313
|
def _get_serializer_class_from_schema_name(schema_name: str):
|
|
316
314
|
"""Try to get the serializer class from schema name."""
|
|
317
315
|
try:
|
|
318
|
-
# Import Django and get all installed apps
|
|
319
|
-
import django
|
|
320
|
-
from django.apps import apps
|
|
321
|
-
from rest_framework import serializers
|
|
322
|
-
|
|
323
316
|
# Search through all apps for the serializer
|
|
324
317
|
for app in apps.get_app_configs():
|
|
325
318
|
app_module = app.module
|
|
326
319
|
try:
|
|
327
320
|
# Try to import serializers module from the app
|
|
328
|
-
serializers_module = __import__(
|
|
329
|
-
|
|
321
|
+
serializers_module = __import__(
|
|
322
|
+
f"{app_module.__name__}.serializers", fromlist=[""]
|
|
323
|
+
)
|
|
324
|
+
|
|
330
325
|
# Look for serializer class matching the schema name
|
|
331
326
|
for attr_name in dir(serializers_module):
|
|
332
327
|
attr = getattr(serializers_module, attr_name)
|
|
333
|
-
if (
|
|
334
|
-
|
|
335
|
-
attr.
|
|
328
|
+
if (
|
|
329
|
+
isinstance(attr, type)
|
|
330
|
+
and issubclass(attr, serializers.Serializer)
|
|
331
|
+
and attr.__name__.replace("Serializer", "") in schema_name
|
|
332
|
+
):
|
|
336
333
|
return attr
|
|
337
334
|
except ImportError:
|
|
338
335
|
continue
|
|
339
|
-
|
|
336
|
+
|
|
340
337
|
except Exception:
|
|
341
|
-
|
|
338
|
+
logger.exception("Failed to get serializser.")
|
|
342
339
|
return None
|
|
343
340
|
|
|
344
341
|
|
|
@@ -362,35 +359,38 @@ def schema_to_example_json(schema: dict, components: dict, for_response: bool =
|
|
|
362
359
|
return _generate_example_by_type(schema, components, for_response)
|
|
363
360
|
|
|
364
361
|
|
|
365
|
-
def _enhance_method_field_schema(schema: dict,
|
|
362
|
+
def _enhance_method_field_schema(schema: dict, _components: dict) -> dict:
|
|
366
363
|
"""Enhance schema by analyzing SerializerMethodField types."""
|
|
367
|
-
if not isinstance(schema, dict) or
|
|
364
|
+
if not isinstance(schema, dict) or "properties" not in schema:
|
|
368
365
|
return schema
|
|
369
|
-
|
|
366
|
+
|
|
370
367
|
# Try to get serializer class from schema title or other hints
|
|
371
|
-
schema_title = schema.get(
|
|
368
|
+
schema_title = schema.get("title", "")
|
|
372
369
|
serializer_class = _get_serializer_class_from_schema_name(schema_title)
|
|
373
|
-
|
|
370
|
+
|
|
374
371
|
if not serializer_class:
|
|
375
372
|
return schema
|
|
376
|
-
|
|
373
|
+
|
|
377
374
|
enhanced_properties = {}
|
|
378
|
-
for prop_name, prop_schema in schema[
|
|
375
|
+
for prop_name, prop_schema in schema["properties"].items():
|
|
379
376
|
# Check if this looks like an unanalyzed SerializerMethodField
|
|
380
|
-
if (
|
|
381
|
-
prop_schema
|
|
382
|
-
|
|
383
|
-
not prop_schema.get(
|
|
384
|
-
not prop_schema.get(
|
|
385
|
-
|
|
377
|
+
if (
|
|
378
|
+
isinstance(prop_schema, dict)
|
|
379
|
+
and prop_schema.get("type") == "string"
|
|
380
|
+
and not prop_schema.get("enum")
|
|
381
|
+
and not prop_schema.get("format")
|
|
382
|
+
and not prop_schema.get("example")
|
|
383
|
+
):
|
|
386
384
|
# Try to analyze the method field
|
|
387
|
-
analyzed_schema = analyze_serializer_method_field_schema(
|
|
385
|
+
analyzed_schema = analyze_serializer_method_field_schema(
|
|
386
|
+
serializer_class, prop_name
|
|
387
|
+
)
|
|
388
388
|
enhanced_properties[prop_name] = analyzed_schema
|
|
389
389
|
else:
|
|
390
390
|
enhanced_properties[prop_name] = prop_schema
|
|
391
|
-
|
|
391
|
+
|
|
392
392
|
enhanced_schema = schema.copy()
|
|
393
|
-
enhanced_schema[
|
|
393
|
+
enhanced_schema["properties"] = enhanced_properties
|
|
394
394
|
return enhanced_schema
|
|
395
395
|
|
|
396
396
|
|
|
@@ -942,4 +942,3 @@ def create_endpoints_index(
|
|
|
942
942
|
]
|
|
943
943
|
)
|
|
944
944
|
generator.create_endpoints_index(endpoints_by_app, docs_dir)
|
|
945
|
-
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
"""Query parameter extraction utilities for Django views."""
|
|
4
|
-
|
|
5
1
|
from typing import Any
|
|
6
2
|
|
|
3
|
+
import django_filters
|
|
4
|
+
|
|
7
5
|
from drf_to_mkdoc.utils.common import extract_viewset_from_operation_id
|
|
8
6
|
|
|
9
7
|
|
|
@@ -51,7 +49,7 @@ def extract_query_parameters_from_view_filter_fields(view_class: Any) -> list[st
|
|
|
51
49
|
elif hasattr(view_class, "filterset_fields") and view_class.filterset_fields:
|
|
52
50
|
filter_fields = sorted(view_class.filterset_fields)
|
|
53
51
|
|
|
54
|
-
return filter_fields
|
|
52
|
+
return list(set(filter_fields))
|
|
55
53
|
|
|
56
54
|
|
|
57
55
|
def extract_query_parameters_from_view_ordering_fields(view_class: Any) -> list[str]:
|
|
@@ -116,8 +114,6 @@ def _extract_filterset_fields_from_class_attributes(filterset_class: Any) -> lis
|
|
|
116
114
|
fields = []
|
|
117
115
|
|
|
118
116
|
try:
|
|
119
|
-
import django_filters
|
|
120
|
-
|
|
121
117
|
# Get all class attributes, including inherited ones
|
|
122
118
|
for attr_name in dir(filterset_class):
|
|
123
119
|
# Skip private attributes and known non-filter attributes
|
|
@@ -186,6 +182,10 @@ def _extract_filterset_fields_from_internal_attrs(filterset_class: Any) -> list[
|
|
|
186
182
|
|
|
187
183
|
|
|
188
184
|
def _extract_filterset_fields_from_get_fields(filterset_class: Any) -> list[str]:
|
|
185
|
+
if not hasattr(filterset_class, "_meta"):
|
|
186
|
+
# If the Meta class is not defined in the Filter class,
|
|
187
|
+
# the get_fields function is raise error
|
|
188
|
+
return []
|
|
189
189
|
fields = []
|
|
190
190
|
|
|
191
191
|
# Try get_fields() method if available (for dynamic filters)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: drf-to-mkdoc
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.7
|
|
4
4
|
Summary: Generate Markdown API docs from Django/DRF OpenAPI schema for MkDocs
|
|
5
5
|
Author-email: Hossein Shayesteh <shayestehhs1@gmail.com>
|
|
6
6
|
Maintainer-email: Hossein Shayesteh <shayestehhs1@gmail.com>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
drf_to_mkdoc/__init__.py,sha256=IbTW5uKQvJRG9ncHRuk_AGKHPn4ruxs5LqDpUFgUhws,180
|
|
2
|
+
drf_to_mkdoc/apps.py,sha256=-NrC_dRr6GmLmNQhkNh819B7V1SS4DYDv5JOR0TtuJM,560
|
|
3
|
+
drf_to_mkdoc/conf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
drf_to_mkdoc/conf/defaults.py,sha256=hEjQdN3WsT539UWhFkylI60piykV_7BDudLPNd_E8PE,575
|
|
5
|
+
drf_to_mkdoc/conf/settings.py,sha256=Gz2Ye5AmPRtOZ3R71IraeK2xBndqEezos9W9xPWYRPY,1513
|
|
6
|
+
drf_to_mkdoc/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
drf_to_mkdoc/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
drf_to_mkdoc/management/commands/build_docs.py,sha256=QUDE6ROwT6LYAfCc4MkzJZANkGF1BdAQIHfvUwuLYG4,4053
|
|
9
|
+
drf_to_mkdoc/management/commands/generate_doc_json.py,sha256=mWdYgMbSeLP4iQZeUm2DxwYQmdGy8w05XTEfbT_nOJo,19833
|
|
10
|
+
drf_to_mkdoc/management/commands/generate_docs.py,sha256=JWoMYvfvjYq_Rlmi7RRFfjJ6qsRjI1K-7B5w-qmjMe8,4896
|
|
11
|
+
drf_to_mkdoc/management/commands/generate_model_docs.py,sha256=A_Q6o10kfy-GN_ZDD9YS6jv3RTyxBy28DEsi5qKZZoU,13421
|
|
12
|
+
drf_to_mkdoc/management/commands/update_doc_schema.py,sha256=TtHVQxnVpB_VELRkVcdsDXDU5zXdguFleB1mXCDMAbg,2009
|
|
13
|
+
drf_to_mkdoc/static/drf-to-mkdoc/javascripts/endpoints-filter.js,sha256=KtfWroqsXg-wwmk36hpoH--M9WIW85EFNGeswMjFu4g,6138
|
|
14
|
+
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/extra.css,sha256=jR52Kyld8fbHCUSehgU0DONcGnjTkqihQqiAXkAvflU,8024
|
|
15
|
+
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/accessibility.css,sha256=DwCGPoaxaUvyifryrKqmkFDH06XBNf65kYsflMTbi0M,494
|
|
16
|
+
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/animations.css,sha256=61m9SLAbatVUNuFeTUTxktoMu9SskYcwFjTsHYbsCRo,393
|
|
17
|
+
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/badges.css,sha256=kUlUcf72uRw6x6Gn7cUq_aTuSGBbhumZ4Us-eBDM7DM,1251
|
|
18
|
+
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/base.css,sha256=iV4ajcRRJ_C3ElzumxTch8u8gRjN-voEs94kDfRpLXc,362
|
|
19
|
+
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/endpoint-content.css,sha256=GfCJD3PGkevMp_t4YPkDto_B024NayuLqx0R9gP-s78,966
|
|
20
|
+
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/endpoints-grid.css,sha256=oNmh9fVgAaw9uvo7sxLEe6AoyTZmr9dF5yiS7qG42Pg,1457
|
|
21
|
+
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/filter-section.css,sha256=MDsGVwgFMhP28pegPLJFn3_GJqVSWZAoy6quz5_9Gz0,4424
|
|
22
|
+
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/fixes.css,sha256=MIPiwWXkSMt5I_q6jN6X5CvWTBmq1CERKZffhvf3sKM,720
|
|
23
|
+
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/layout.css,sha256=dyoTUJ5Y8njxdWE6t2G3nRS-bT0BT04UMat8K6yW2y4,637
|
|
24
|
+
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/loading.css,sha256=C_wZjMw5Cs2inpg892S0_E7_m5bT8BwZfVBRQfvIcKw,712
|
|
25
|
+
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/responsive.css,sha256=E6q6sHftBm-ZL2OvSvZ8JHP0bYnhudyQCcPSujDiZQY,1462
|
|
26
|
+
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/sections.css,sha256=xdrO6vUpthFFN1ESummoGuG5MPtE2d2lPsBOWuv-T6o,705
|
|
27
|
+
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/stats.css,sha256=0cDD8s63r6zQid_O1schNvfIwys1Y526xO6-B6s4Lxc,667
|
|
28
|
+
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/tags.css,sha256=dOw13qsvVjx9cibzgzXlOutXVosNp6LzFfEFKvumG8w,1785
|
|
29
|
+
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/theme-toggle.css,sha256=atnaTx7Ed6YZ0sIfofYBf8wOfUzlLCynrBDMAjHJF58,676
|
|
30
|
+
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/variables.css,sha256=JJpvo4MoEsPEiFfugNDbPWvYAGDh0kE1jEl804dX3sk,830
|
|
31
|
+
drf_to_mkdoc/utils/__init__.py,sha256=6dFTb07S6yIf-INMy0Mlgf5purNir687ZU9WZtITh4k,68
|
|
32
|
+
drf_to_mkdoc/utils/common.py,sha256=LHq2MjqURLK6VOXNfjWXPADXPDYxxYbcmLZs9jIg36Y,11744
|
|
33
|
+
drf_to_mkdoc/utils/endpoint_generator.py,sha256=2V63lvik_dlxLZ0P4WrNe77iboFLKpUZ9VJAL758tC4,35716
|
|
34
|
+
drf_to_mkdoc/utils/model_generator.py,sha256=O1ibaw7KmL_fQ1OTebuk6Tt2yTjyElpyF7bN8gk5LBE,9588
|
|
35
|
+
drf_to_mkdoc/utils/extractors/__init__.py,sha256=BvC8gKOPVI9gU1Piw0jRhKQ2ER5s1moAxgq7ZYkJWNI,86
|
|
36
|
+
drf_to_mkdoc/utils/extractors/query_parameter_extractors.py,sha256=-u9TG1hDKVExanypyWM7wQGG9vo80WrfVLbaPXv-Bc4,8494
|
|
37
|
+
drf_to_mkdoc/utils/md_generators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
|
+
drf_to_mkdoc/utils/md_generators/query_parameters_generators.py,sha256=N-XqZ_FUODSR5V4xM9oEA3aaIiNGNmNwpvrWbQTx6RI,2566
|
|
39
|
+
drf_to_mkdoc-1.0.7.dist-info/licenses/LICENSE,sha256=3n9_ckIREsH8ogCxWW6dFsw_WfGcluG2mHcgl9i_UU0,1068
|
|
40
|
+
drf_to_mkdoc-1.0.7.dist-info/METADATA,sha256=VBPSLEF8EX1LaRDMe31AhZoihyEPBTQcW_50z9lL2lM,7304
|
|
41
|
+
drf_to_mkdoc-1.0.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
42
|
+
drf_to_mkdoc-1.0.7.dist-info/top_level.txt,sha256=ZzJecR6j_tvLZiubUBEgawHflozC4DQy9ooNf1yDJ3Q,13
|
|
43
|
+
drf_to_mkdoc-1.0.7.dist-info/RECORD,,
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
drf_to_mkdoc/__init__.py,sha256=j7qOxIbkDy7oit4Tb3NZUqbqkrxKz07PeN9QuF3Qp9s,179
|
|
2
|
-
drf_to_mkdoc/apps.py,sha256=0TLecPHZ8vf0IhAVFh1oIIHQbhx5lVto7qrvStx3R1Y,464
|
|
3
|
-
drf_to_mkdoc/conf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
drf_to_mkdoc/conf/defaults.py,sha256=9OK65SeP4aLZbuRJBAE_QeC-OhXkh0cACBqax6wYXnM,576
|
|
5
|
-
drf_to_mkdoc/conf/settings.py,sha256=OgB3MCn4Z5F4xqWP34kwzMs50kRn3qF0gE1zS2SHS2M,1550
|
|
6
|
-
drf_to_mkdoc/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
-
drf_to_mkdoc/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
drf_to_mkdoc/management/commands/build_docs.py,sha256=p--TajkpYU-hmthXp5eVmVtJnXLxZrfQWQmuzwOgl0c,2491
|
|
9
|
-
drf_to_mkdoc/management/commands/generate_doc_json.py,sha256=mWdYgMbSeLP4iQZeUm2DxwYQmdGy8w05XTEfbT_nOJo,19833
|
|
10
|
-
drf_to_mkdoc/management/commands/generate_docs.py,sha256=YGdejd-b1Wn_e5ru9orwp1b9H5PZwVWkuWxAY1JyG88,4897
|
|
11
|
-
drf_to_mkdoc/management/commands/generate_model_docs.py,sha256=tdT9Z0qjZ9KgGAbFfYWBo-FtDI8wTQ2zRA_OvKKnyaA,12195
|
|
12
|
-
drf_to_mkdoc/management/commands/update_doc_schema.py,sha256=TtHVQxnVpB_VELRkVcdsDXDU5zXdguFleB1mXCDMAbg,2009
|
|
13
|
-
drf_to_mkdoc/utils/__init__.py,sha256=6dFTb07S6yIf-INMy0Mlgf5purNir687ZU9WZtITh4k,68
|
|
14
|
-
drf_to_mkdoc/utils/common.py,sha256=BIzN9iDOphIHPGkRGj8iVhY8lAUdvBAyNazP3wDmz9c,11814
|
|
15
|
-
drf_to_mkdoc/utils/endpoint_generator.py,sha256=oGHQXJB5VFlGOq6W8a3q96CwF3conjBe_tkYj6m2mlg,35849
|
|
16
|
-
drf_to_mkdoc/utils/model_generator.py,sha256=O1ibaw7KmL_fQ1OTebuk6Tt2yTjyElpyF7bN8gk5LBE,9588
|
|
17
|
-
drf_to_mkdoc/utils/extractors/__init__.py,sha256=BvC8gKOPVI9gU1Piw0jRhKQ2ER5s1moAxgq7ZYkJWNI,86
|
|
18
|
-
drf_to_mkdoc/utils/extractors/query_parameter_extractors.py,sha256=e7WW0MeLUfBAfksEKFxowDjz9uUvit_EDxYASfnbdc4,8400
|
|
19
|
-
drf_to_mkdoc/utils/md_generators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
-
drf_to_mkdoc/utils/md_generators/query_parameters_generators.py,sha256=N-XqZ_FUODSR5V4xM9oEA3aaIiNGNmNwpvrWbQTx6RI,2566
|
|
21
|
-
drf_to_mkdoc-0.1.5.dist-info/licenses/LICENSE,sha256=3n9_ckIREsH8ogCxWW6dFsw_WfGcluG2mHcgl9i_UU0,1068
|
|
22
|
-
drf_to_mkdoc-0.1.5.dist-info/METADATA,sha256=qFgbvjEKTuW_DP6VlK3YsJZGIg1NYY1b35j5TdQ4YZU,7304
|
|
23
|
-
drf_to_mkdoc-0.1.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
24
|
-
drf_to_mkdoc-0.1.5.dist-info/top_level.txt,sha256=ZzJecR6j_tvLZiubUBEgawHflozC4DQy9ooNf1yDJ3Q,13
|
|
25
|
-
drf_to_mkdoc-0.1.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|