drf-to-mkdoc 0.2.0__py3-none-any.whl → 0.2.1__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.

Files changed (33) hide show
  1. drf_to_mkdoc/conf/defaults.py +5 -0
  2. drf_to_mkdoc/conf/settings.py +123 -9
  3. drf_to_mkdoc/management/commands/build_docs.py +8 -7
  4. drf_to_mkdoc/management/commands/build_endpoint_docs.py +69 -0
  5. drf_to_mkdoc/management/commands/build_model_docs.py +50 -0
  6. drf_to_mkdoc/management/commands/{generate_model_docs.py → extract_model_data.py} +14 -19
  7. drf_to_mkdoc/utils/ai_tools/__init__.py +0 -0
  8. drf_to_mkdoc/utils/ai_tools/enums.py +13 -0
  9. drf_to_mkdoc/utils/ai_tools/exceptions.py +19 -0
  10. drf_to_mkdoc/utils/ai_tools/providers/__init__.py +0 -0
  11. drf_to_mkdoc/utils/ai_tools/providers/base_provider.py +123 -0
  12. drf_to_mkdoc/utils/ai_tools/providers/gemini_provider.py +80 -0
  13. drf_to_mkdoc/utils/ai_tools/types.py +81 -0
  14. drf_to_mkdoc/utils/commons/__init__.py +0 -0
  15. drf_to_mkdoc/utils/commons/code_extractor.py +22 -0
  16. drf_to_mkdoc/utils/commons/file_utils.py +35 -0
  17. drf_to_mkdoc/utils/commons/model_utils.py +83 -0
  18. drf_to_mkdoc/utils/commons/operation_utils.py +83 -0
  19. drf_to_mkdoc/utils/commons/path_utils.py +78 -0
  20. drf_to_mkdoc/utils/commons/schema_utils.py +230 -0
  21. drf_to_mkdoc/utils/endpoint_detail_generator.py +7 -34
  22. drf_to_mkdoc/utils/endpoint_list_generator.py +1 -1
  23. drf_to_mkdoc/utils/extractors/query_parameter_extractors.py +33 -30
  24. drf_to_mkdoc/utils/model_detail_generator.py +21 -15
  25. drf_to_mkdoc/utils/model_list_generator.py +25 -15
  26. drf_to_mkdoc/utils/schema.py +259 -0
  27. {drf_to_mkdoc-0.2.0.dist-info → drf_to_mkdoc-0.2.1.dist-info}/METADATA +16 -5
  28. {drf_to_mkdoc-0.2.0.dist-info → drf_to_mkdoc-0.2.1.dist-info}/RECORD +31 -16
  29. drf_to_mkdoc/management/commands/generate_docs.py +0 -113
  30. drf_to_mkdoc/utils/common.py +0 -353
  31. {drf_to_mkdoc-0.2.0.dist-info → drf_to_mkdoc-0.2.1.dist-info}/WHEEL +0 -0
  32. {drf_to_mkdoc-0.2.0.dist-info → drf_to_mkdoc-0.2.1.dist-info}/licenses/LICENSE +0 -0
  33. {drf_to_mkdoc-0.2.0.dist-info → drf_to_mkdoc-0.2.1.dist-info}/top_level.txt +0 -0
@@ -4,7 +4,7 @@ from typing import Any
4
4
  from django.templatetags.static import static
5
5
 
6
6
  from drf_to_mkdoc.conf.settings import drf_to_mkdoc_settings
7
- from drf_to_mkdoc.utils.common import extract_viewset_from_operation_id
7
+ from drf_to_mkdoc.utils.commons.operation_utils import extract_viewset_from_operation_id
8
8
 
9
9
 
10
10
  class EndpointsIndexGenerator:
@@ -1,8 +1,6 @@
1
1
  from typing import Any
2
2
 
3
- import django_filters
4
-
5
- from drf_to_mkdoc.utils.common import extract_viewset_from_operation_id
3
+ from drf_to_mkdoc.utils.commons.operation_utils import extract_viewset_from_operation_id
6
4
 
7
5
 
8
6
  def extract_query_parameters_from_view(operation_id: str) -> dict[str, Any]:
@@ -111,35 +109,34 @@ def extract_query_parameters_from_view_pagination_fields(view_class: Any) -> lis
111
109
 
112
110
 
113
111
  def _extract_filterset_fields_from_class_attributes(filterset_class: Any) -> list[str]:
114
- fields = []
115
-
116
112
  try:
117
- # Get all class attributes, including inherited ones
118
- for attr_name in dir(filterset_class):
119
- # Skip private attributes and known non-filter attributes
120
- if attr_name.startswith("_") or attr_name in [
121
- "Meta",
122
- "form",
123
- "queryset",
124
- "request",
125
- "errors",
126
- "qs",
127
- "is_valid",
128
- ]:
129
- continue
130
-
131
- try:
132
- attr = getattr(filterset_class, attr_name)
133
- if isinstance(attr, django_filters.Filter):
134
- if attr_name not in fields:
135
- fields.append(attr_name)
136
- except (AttributeError, TypeError):
137
- continue
138
-
113
+ import django_filters # noqa: PLC0415
139
114
  except ImportError:
140
115
  # django_filters not available, skip this strategy
141
- pass
116
+ return []
142
117
 
118
+ fields = []
119
+ # Get all class attributes, including inherited ones
120
+ for attr_name in dir(filterset_class):
121
+ # Skip private attributes and known non-filter attributes
122
+ if attr_name.startswith("_") or attr_name in [
123
+ "Meta",
124
+ "form",
125
+ "queryset",
126
+ "request",
127
+ "errors",
128
+ "qs",
129
+ "is_valid",
130
+ ]:
131
+ continue
132
+
133
+ try:
134
+ attr = getattr(filterset_class, attr_name)
135
+ if isinstance(attr, django_filters.Filter):
136
+ if attr_name not in fields:
137
+ fields.append(attr_name)
138
+ except (AttributeError, TypeError):
139
+ continue
143
140
  return fields
144
141
 
145
142
 
@@ -182,7 +179,8 @@ def _extract_filterset_fields_from_internal_attrs(filterset_class: Any) -> list[
182
179
 
183
180
 
184
181
  def _extract_filterset_fields_from_get_fields(filterset_class: Any) -> list[str]:
185
- if not (filterset_class._meta and filterset_class._meta.model):
182
+ meta = getattr(filterset_class, "_meta", None)
183
+ if not getattr(meta, "model", None):
186
184
  # If the Meta class is not defined in the Filter class,
187
185
  # the get_fields function is raise error
188
186
  return []
@@ -191,7 +189,12 @@ def _extract_filterset_fields_from_get_fields(filterset_class: Any) -> list[str]
191
189
  if not hasattr(filterset_class, "get_fields"):
192
190
  return []
193
191
 
194
- filterset_instance = filterset_class()
192
+ try:
193
+ filterset_instance = filterset_class()
194
+ except TypeError:
195
+ # Constructor requires args; skip dynamic field discovery
196
+ return []
197
+
195
198
  filterset_fields = filterset_instance.get_fields()
196
199
  if not (filterset_fields and hasattr(filterset_fields, "keys")):
197
200
  return []
@@ -3,21 +3,28 @@ from typing import Any
3
3
  from django.templatetags.static import static
4
4
 
5
5
  from drf_to_mkdoc.conf.settings import drf_to_mkdoc_settings
6
- from drf_to_mkdoc.utils.common import get_model_description, write_file
6
+ from drf_to_mkdoc.utils.commons.file_utils import write_file
7
+ from drf_to_mkdoc.utils.commons.model_utils import get_model_description
7
8
 
8
9
 
9
10
  def generate_model_docs(models_data: dict[str, Any]) -> None:
10
11
  """Generate model documentation from JSON data"""
11
- for model_name, model_info in models_data.items():
12
- app_name = model_info.get("app_label", model_name.split(".")[0])
13
- class_name = model_info.get("name", model_name.split(".")[-1])
12
+ for app_name, models in models_data.items():
13
+ if not isinstance(models, dict):
14
+ raise TypeError(f"Expected dict for models in app '{app_name}', got {type(models)}")
14
15
 
15
- # Create the model page content
16
- content = create_model_page(model_info)
16
+ for model_name, model_info in models.items():
17
+ if not isinstance(model_info, dict) or "name" not in model_info:
18
+ raise ValueError(
19
+ f"Model info for '{model_name}' in app '{app_name}' is invalid"
20
+ )
17
21
 
18
- # Write the file in app subdirectory
19
- file_path = f"models/{app_name}/{class_name.lower()}.md"
20
- write_file(file_path, content)
22
+ # Create the model page content
23
+ content = create_model_page(model_info)
24
+
25
+ # Write the file in app subdirectory
26
+ file_path = f"models/{app_name}/{model_info['table_name']}.md"
27
+ write_file(file_path, content)
21
28
 
22
29
 
23
30
  def render_column_fields_table(fields: dict[str, Any]) -> str:
@@ -181,13 +188,12 @@ def _render_relationships_from_section(relationships: dict[str, Any]) -> str:
181
188
  content = ""
182
189
  for rel_name, rel_info in relationships.items():
183
190
  rel_type = rel_info.get("type", "Unknown")
184
- related_model_full = rel_info.get("related_model", "")
185
191
 
186
- if related_model_full and "." in related_model_full:
187
- related_app, related_model = related_model_full.split(".", 1)
188
- model_link = f"[{related_model}](../../{related_app}/{related_model.lower()}/)"
189
- else:
190
- model_link = related_model_full
192
+ app_label = rel_info["app_label"]
193
+ table_name = rel_info["table_name"]
194
+ verbose_name = rel_info["verbose_name"]
195
+
196
+ model_link = f"[{verbose_name.capitalize()}](../../{app_label}/{table_name}/)"
191
197
 
192
198
  content += f"| `{rel_name}` | {rel_type} | {model_link} | \n"
193
199
 
@@ -1,22 +1,16 @@
1
+ from html import escape
1
2
  from pathlib import Path
2
3
  from typing import Any
4
+ from urllib.parse import quote
3
5
 
4
6
  from django.templatetags.static import static
5
7
 
6
8
  from drf_to_mkdoc.conf.settings import drf_to_mkdoc_settings
7
- from drf_to_mkdoc.utils.common import get_app_descriptions
9
+ from drf_to_mkdoc.utils.commons.model_utils import get_app_descriptions
8
10
 
9
11
 
10
12
  def create_models_index(models_data: dict[str, Any], docs_dir: Path) -> None:
11
13
  """Create the main models index page that lists all models organized by app."""
12
- models_by_app = {}
13
- for model_name, model_info in models_data.items():
14
- app_name = model_info.get("app_label", model_name.split(".")[0])
15
- class_name = model_info.get("name", model_name.split(".")[-1])
16
- if app_name not in models_by_app:
17
- models_by_app[app_name] = []
18
- models_by_app[app_name].append((class_name, model_name, model_info))
19
-
20
14
  stylesheets = [
21
15
  "stylesheets/models/variables.css",
22
16
  "stylesheets/models/base.css",
@@ -40,17 +34,33 @@ This section contains documentation for all Django models in the system, organiz
40
34
 
41
35
  app_descriptions = get_app_descriptions()
42
36
 
43
- for app_name in sorted(models_by_app.keys()):
44
- app_desc = app_descriptions.get(app_name, f"{app_name.title()} application models")
45
- content += f'<div class="app-header">{app_name.title()} App</div>\n'
37
+ for app_name, models in sorted(models_data.items()):
38
+ app_desc = escape(
39
+ app_descriptions.get(
40
+ app_name, f"{app_name.replace('_', ' ').title()} application models"
41
+ )
42
+ )
43
+ app_title = escape(app_name.replace("_", " ").title())
44
+ content += f'<div class="app-header">{app_title} App</div>\n'
46
45
  content += f'<div class="app-description">{app_desc}</div>\n\n'
47
46
 
48
47
  content += '<div class="model-cards">\n'
49
48
 
50
- for class_name, _model_name, _model_info in sorted(models_by_app[app_name]):
49
+ model_names = sorted(
50
+ [
51
+ (
52
+ str(mi.get("verbose_name") or mk),
53
+ str(mi.get("table_name") or mk),
54
+ )
55
+ for mk, mi in models.items()
56
+ if isinstance(mi, dict)
57
+ ],
58
+ key=lambda x: x[0].casefold(),
59
+ )
60
+ for verbose_name, table_name in model_names:
51
61
  content += f"""
52
- <a href="{app_name}/{class_name.lower()}/"
53
- class="model-card">{class_name}</a>\n
62
+ <a href="{quote(app_name, safe="")}/{quote(table_name, safe="")}/"
63
+ class="model-card">{escape(verbose_name)}</a>\n
54
64
  """
55
65
 
56
66
  content += "</div>\n\n"
@@ -0,0 +1,259 @@
1
+ import inspect
2
+ import logging
3
+ from importlib import import_module
4
+ from types import SimpleNamespace
5
+ from typing import Any
6
+
7
+ from drf_spectacular.openapi import AutoSchema as SpectacularAutoSchema
8
+ from drf_spectacular.plumbing import ComponentRegistry
9
+ from rest_framework.serializers import BaseSerializer, ListSerializer
10
+ from rest_framework.viewsets import ViewSetMixin
11
+
12
+ from drf_to_mkdoc.conf.settings import drf_to_mkdoc_settings
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class ViewMetadataExtractor:
18
+ """Extracts metadata from DRF views."""
19
+
20
+ def __init__(self, view, callback, method):
21
+ self.view = view
22
+ self.callback = callback
23
+ self.method = method
24
+ self.view_instance = None
25
+ self.action = None
26
+ self.error_message = None
27
+
28
+ def _create_view_instance(self):
29
+ """Create view instance for introspection."""
30
+ try:
31
+ self.view_instance = self.view()
32
+ except (TypeError, AttributeError, ImportError) as e:
33
+ self.error_message = str(e)
34
+ return False
35
+ else:
36
+ return True
37
+
38
+ def _extract_permissions(self):
39
+ """Extract permission classes from view."""
40
+ permission_classes = []
41
+ if hasattr(self.view, "permission_classes"):
42
+ for perm_class in self.view.permission_classes:
43
+ permission_classes.append(f"{perm_class.__module__}.{perm_class.__name__}")
44
+ return permission_classes
45
+
46
+ def _extract_action(self):
47
+ """Extract action name from ViewSet."""
48
+ if isinstance(self.view_instance, ViewSetMixin):
49
+ self.action = self.callback.actions.get(self.method.lower())
50
+ if self.action:
51
+ self.view_instance.action = self.action
52
+ self.view_instance.request = SimpleNamespace(method=self.method.upper())
53
+
54
+ def _extract_serializer_from_view_instance(self):
55
+ """Try to get serializer class from view instance."""
56
+ if not hasattr(self.view_instance, "get_serializer_class"):
57
+ return None
58
+
59
+ try:
60
+ serializer_cls = self.view_instance.get_serializer_class()
61
+ except (AttributeError, TypeError, ImportError) as e:
62
+ logger.debug(f"Failed to get serializer from view instance: {e}")
63
+ return None
64
+ else:
65
+ return f"{serializer_cls.__module__}.{serializer_cls.__name__}"
66
+
67
+ def _extract_serializer_from_action(self):
68
+ """Try to get serializer class from action method."""
69
+ if not self.action:
70
+ return None
71
+
72
+ action_method = getattr(self.view, self.action, None)
73
+ if not (action_method and callable(action_method)):
74
+ return None
75
+
76
+ if hasattr(action_method, "serializer_class"):
77
+ serializer_cls = action_method.serializer_class
78
+ return f"{serializer_cls.__module__}.{serializer_cls.__name__}"
79
+ if hasattr(action_method, "kwargs") and "serializer_class" in action_method.kwargs:
80
+ serializer_cls = action_method.kwargs["serializer_class"]
81
+ return f"{serializer_cls.__module__}.{serializer_cls.__name__}"
82
+
83
+ return None
84
+
85
+ def _extract_serializer_from_class(self):
86
+ """Try to get serializer class from view class."""
87
+ if hasattr(self.view, "serializer_class") and self.view.serializer_class:
88
+ serializer_cls = self.view.serializer_class
89
+ return f"{serializer_cls.__module__}.{serializer_cls.__name__}"
90
+ return None
91
+
92
+ def _extract_action_source(self):
93
+ """Get action source info if no serializer found."""
94
+ if not self.action:
95
+ return {}
96
+
97
+ action_method = getattr(self.view, self.action, None)
98
+ if not (action_method and callable(action_method)):
99
+ return {}
100
+
101
+ return {
102
+ "importable_path": f"{self.view.__module__}.{self.view.__name__}.{self.action}",
103
+ "module": self.view.__module__,
104
+ "class_name": self.view.__name__,
105
+ "method_name": self.action,
106
+ }
107
+
108
+ def _extract_serializer_parents(self, serializer_cls):
109
+ """Extract parent classes of serializer up to specified depth."""
110
+ if not serializer_cls:
111
+ return []
112
+
113
+ parents = []
114
+ for base in inspect.getmro(serializer_cls)[
115
+ 1 : drf_to_mkdoc_settings.SERIALIZERS_INHERITANCE_DEPTH + 1
116
+ ]:
117
+ if base is object or base.__module__ == "builtins":
118
+ continue
119
+ parents.append(f"{base.__module__}.{base.__name__}")
120
+
121
+ return parents
122
+
123
+ def _extract_serializer_attrs(self, serializer_cls):
124
+ """Extract nested serializer attributes up to specified depth."""
125
+ if not serializer_cls:
126
+ return {}
127
+
128
+ attrs = {}
129
+ try:
130
+ serializer_instance = serializer_cls()
131
+ for field_name, field in getattr(serializer_instance, "fields", {}).items():
132
+ if isinstance(field, ListSerializer) and isinstance(
133
+ field.child, BaseSerializer
134
+ ):
135
+ # Handle ListSerializer and similar fields
136
+ child_class = field.child.__class__
137
+ attrs[field_name] = {
138
+ "type": "list",
139
+ "child_serializer": f"{child_class.__module__}.{child_class.__name__}",
140
+ }
141
+ elif isinstance(field, BaseSerializer):
142
+ # Handle nested serializers
143
+ nested_class = field.__class__
144
+ attrs[field_name] = {
145
+ "type": "nested",
146
+ "serializer": f"{nested_class.__module__}.{nested_class.__name__}",
147
+ }
148
+ except (TypeError, AttributeError) as e:
149
+ logger.debug(f"Failed to extract serializer attributes: {e}")
150
+
151
+ return attrs
152
+
153
+ def extract(self):
154
+ """Extract all metadata from view."""
155
+ if not self._create_view_instance():
156
+ return {
157
+ "view_class": f"{self.view.__module__}.{self.view.__name__}",
158
+ "action": None,
159
+ "serializer_class": None,
160
+ "permission_classes": [],
161
+ "error_message": str(self.error_message),
162
+ "action_source": {},
163
+ "serializer_parents": [],
164
+ "serializer_attrs": {},
165
+ }
166
+
167
+ permission_classes = self._extract_permissions()
168
+ self._extract_action()
169
+
170
+ serializer_class = None
171
+ serializer_class_str = (
172
+ self._extract_serializer_from_view_instance()
173
+ or self._extract_serializer_from_action()
174
+ or self._extract_serializer_from_class()
175
+ )
176
+
177
+ if serializer_class_str:
178
+ module_name, class_name = serializer_class_str.rsplit(".", 1)
179
+ try:
180
+ module = import_module(module_name)
181
+ serializer_class = getattr(module, class_name)
182
+ except (ImportError, AttributeError) as e:
183
+ logger.debug(f"Failed to import serializer class: {e}")
184
+
185
+ action_source = {} if serializer_class_str else self._extract_action_source()
186
+ serializer_parents = (
187
+ self._extract_serializer_parents(serializer_class) if serializer_class else []
188
+ )
189
+ serializer_attrs = (
190
+ self._extract_serializer_attrs(serializer_class) if serializer_class else {}
191
+ )
192
+
193
+ return {
194
+ "view_class": f"{self.view.__module__}.{self.view.__name__}",
195
+ "action": self.action,
196
+ "serializer_class": serializer_class_str,
197
+ "permission_classes": permission_classes,
198
+ "error_message": str(self.error_message) if self.error_message else None,
199
+ "action_source": action_source,
200
+ "serializer_parents": serializer_parents,
201
+ "serializer_attrs": serializer_attrs,
202
+ }
203
+
204
+
205
+ class AutoSchema(SpectacularAutoSchema):
206
+ """
207
+ Custom AutoSchema that extends drf_spectacular's AutoSchema to add view metadata
208
+ directly to the operation during schema generation instead of using a postprocessing hook.
209
+ """
210
+
211
+ def get_operation(
212
+ self,
213
+ path: str,
214
+ path_regex: str,
215
+ path_prefix: str,
216
+ method: str,
217
+ registry: ComponentRegistry,
218
+ ) -> dict[str, Any] | None:
219
+ # Call the parent's get_operation to get the base operation
220
+ operation = super().get_operation(path, path_regex, path_prefix, method, registry)
221
+
222
+ if operation:
223
+ try:
224
+ # Extract metadata from the view
225
+ view = self.view.__class__
226
+ callback = self._get_callback_obj(method)
227
+ metadata = ViewMetadataExtractor(view, callback, method).extract()
228
+
229
+ # Add metadata to the operation
230
+ operation.setdefault("x-metadata", {})
231
+ operation["x-metadata"].update(metadata)
232
+ except Exception:
233
+ # Log the error but don't break schema generation
234
+ logger.exception("Error adding metadata to operation")
235
+
236
+ return operation
237
+
238
+ def _get_callback_obj(self, method: str):
239
+ """
240
+ Helper method to get the callback object with actions.
241
+ This is needed to extract the action name from the callback.
242
+ """
243
+ # Access the view's action_map or action to determine the mapping
244
+ actions = {}
245
+
246
+ # For ViewSets, the action_map contains the method->action mapping
247
+ if hasattr(self.view, "action_map") and self.view.action_map is not None:
248
+ actions = {m.lower(): a for m, a in self.view.action_map.items()}
249
+ # For APIViews with an explicit action
250
+ elif hasattr(self.view, "action"):
251
+ actions = {method.lower(): self.view.action}
252
+
253
+ # Create a callback-like object with the necessary attributes
254
+ class CallbackObj:
255
+ def __init__(self, view_cls, actions_dict):
256
+ self.cls = view_cls
257
+ self.actions = actions_dict
258
+
259
+ return CallbackObj(self.view.__class__, actions)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drf-to-mkdoc
3
- Version: 0.2.0
3
+ Version: 0.2.1
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>
@@ -76,13 +76,14 @@ INSTALLED_APPS = [
76
76
 
77
77
  # Required for OpenAPI schema generation
78
78
  REST_FRAMEWORK = {
79
- 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
79
+ 'DEFAULT_SCHEMA_CLASS': 'drf_to_mkdoc.utils.schema.AutoSchema', # Use our custom AutoSchema
80
80
  }
81
81
 
82
82
  SPECTACULAR_SETTINGS = {
83
83
  'TITLE': 'Your API',
84
84
  'DESCRIPTION': 'Your API description',
85
85
  'VERSION': '1.0.0',
86
+
86
87
  }
87
88
 
88
89
  DRF_TO_MKDOC = {
@@ -110,10 +111,19 @@ DRF_TO_MKDOC = {
110
111
  python manage.py build_docs --settings=docs_settings
111
112
  ```
112
113
 
114
+ ## Available Commands
115
+
116
+ - `build_docs`: Build the complete documentation site with MkDocs
117
+ - `build_endpoint_docs`: Build endpoint documentation from OpenAPI schema
118
+ - `build_model_docs`: Build model documentation from model JSON data
119
+ - `extract_model_data`: Extract model data from Django model introspection and save as JSON
120
+ - `update_doc_schema`: Update the final schema by copying the documented schema
121
+
113
122
  ## What you get
114
123
 
115
124
  See a detailed overview of generated files in `docs/structure.md` and a feature breakdown in `docs/features.md`.
116
125
 
126
+
117
127
  ## How it works
118
128
 
119
129
  Under the hood, drf-to-mkdoc introspects your models and reads your DRF OpenAPI schema to generate clean, organized Markdown. Then MkDocs turns it into a polished static site. Always current, no manual updates.
@@ -154,8 +164,9 @@ drf-to-mkdoc/
154
164
  │ ├── management/
155
165
  │ │ └── commands/
156
166
  │ │ ├── build_docs.py # Build MkDocs site
157
- │ │ ├── generate_docs.py # Main documentation generator
158
- │ │ ├── generate_model_docs.py # Model documentation
167
+ │ │ ├── build_endpoint_docs.py # Build endpoint documentation
168
+ │ │ ├── build_model_docs.py # Build model documentation
169
+ │ │ ├── extract_model_data.py # Extract model data from Django
159
170
  │ │ └── update_doc_schema.py # Schema updates
160
171
  │ └── utils/
161
172
  │ ├── common.py # Shared utilities
@@ -222,4 +233,4 @@ your-project/
222
233
  ## Contributing
223
234
 
224
235
  See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed contribution guidelines.
225
- This will ensure that only the source configuration and scripts are versioned, while the generated documentation is excluded.
236
+ This will ensure that only the source configuration and scripts are versioned, while the generated documentation is excluded.
@@ -1,14 +1,15 @@
1
1
  drf_to_mkdoc/__init__.py,sha256=IbTW5uKQvJRG9ncHRuk_AGKHPn4ruxs5LqDpUFgUhws,180
2
2
  drf_to_mkdoc/apps.py,sha256=-NrC_dRr6GmLmNQhkNh819B7V1SS4DYDv5JOR0TtuJM,560
3
3
  drf_to_mkdoc/conf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- drf_to_mkdoc/conf/defaults.py,sha256=DZiATgfjkyU_J1RgR_zAzPdOVp-F2o3LVwjb3hP6j0U,660
5
- drf_to_mkdoc/conf/settings.py,sha256=OOna4jWNTEfROBprpKnEHVl5RcAYExmOf0gBMA0E_FY,1664
4
+ drf_to_mkdoc/conf/defaults.py,sha256=iG5ssgaJgDoI92cyCDCHmuY1uvMCeU8T0D3gxrDPWYM,986
5
+ drf_to_mkdoc/conf/settings.py,sha256=BruquzEGvGzJ3YhXnWHn4gxxZA6Ed1Twtry8rokgdUI,5557
6
6
  drf_to_mkdoc/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  drf_to_mkdoc/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- drf_to_mkdoc/management/commands/build_docs.py,sha256=71dDDbb-qjRZr0kugviHtf3W_MSPFVjxkJ0tlLn97zM,4037
8
+ drf_to_mkdoc/management/commands/build_docs.py,sha256=k2N8i7sNWPJGVzUSdkDu47FleCSjIxClRq1dWBHwhjQ,4057
9
+ drf_to_mkdoc/management/commands/build_endpoint_docs.py,sha256=UcKoHFzmsEhs90kHMRvRe2XWx6xigrGAnwA5iEs839s,2450
10
+ drf_to_mkdoc/management/commands/build_model_docs.py,sha256=8d7UjwwIsEsReIH4b93nTqFnrZO8kPHXdQaSTYudUGw,1926
11
+ drf_to_mkdoc/management/commands/extract_model_data.py,sha256=XoMO4C22ZPKQ99bh1WskEUT1JkA3GpDN5wb3_D5cN0I,13583
9
12
  drf_to_mkdoc/management/commands/generate_doc_json.py,sha256=mWdYgMbSeLP4iQZeUm2DxwYQmdGy8w05XTEfbT_nOJo,19833
10
- drf_to_mkdoc/management/commands/generate_docs.py,sha256=yxCuU5gXqpHEyL-U-li-s7X1Hae86Cb-Q2z3xlonw0k,4346
11
- drf_to_mkdoc/management/commands/generate_model_docs.py,sha256=_A-C7gIZhCzFN4g9D8uwrd2yQW-HzgibCu4z_vx0QLM,13477
12
13
  drf_to_mkdoc/management/commands/update_doc_schema.py,sha256=TtHVQxnVpB_VELRkVcdsDXDU5zXdguFleB1mXCDMAbg,2009
13
14
  drf_to_mkdoc/static/drf-to-mkdoc/javascripts/endpoints-filter.js,sha256=KtfWroqsXg-wwmk36hpoH--M9WIW85EFNGeswMjFu4g,6138
14
15
  drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out-sidebar.js,sha256=zvEIm5rxTdMdmzElmmAqniIrI4ms2vsqGOMI0VFb_RQ,38953
@@ -36,17 +37,31 @@ drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/model-tables.css,sha256=8CSy
36
37
  drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/responsive.css,sha256=ygqyUtpiWchTBBIQil1C6mN0AC5xinLoP7whSKfBmwg,944
37
38
  drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/variables.css,sha256=2HvyjnJrygSzdzpE-FYpo6FGhrYhmZ7NwDFAkADXQNg,1094
38
39
  drf_to_mkdoc/utils/__init__.py,sha256=6dFTb07S6yIf-INMy0Mlgf5purNir687ZU9WZtITh4k,68
39
- drf_to_mkdoc/utils/common.py,sha256=SxjGe9MtyxTyroXP7QdUiW9QqZ1N7xPjHHaAI-hqYxc,12077
40
- drf_to_mkdoc/utils/endpoint_detail_generator.py,sha256=vAGQz_DV5wS47hfZNWgI_1oKarLdV5TJ6nwv47uMWw0,27911
41
- drf_to_mkdoc/utils/endpoint_list_generator.py,sha256=P7pEHscHNHpjDuTXmAUPYV3_t8uGSdaYOgiDGGoCwYc,9705
42
- drf_to_mkdoc/utils/model_detail_generator.py,sha256=IHxgEW87Z-kxcFVVWqwdc7k7PsZAwXwixddt-tCvLKg,7961
43
- drf_to_mkdoc/utils/model_list_generator.py,sha256=7gTPwxOxlvvCfDgVPTbGPsLi1gnI47UjMqKc57qRmks,2409
40
+ drf_to_mkdoc/utils/endpoint_detail_generator.py,sha256=1HvnOzqczZNqO8pbXedz6cSQ_pOFPDkmeD9yVxNOz7A,26989
41
+ drf_to_mkdoc/utils/endpoint_list_generator.py,sha256=Mtk93QNkSz_J1m2sQ5_43CmvZW9yEqDaueEx4OhW8XQ,9722
42
+ drf_to_mkdoc/utils/model_detail_generator.py,sha256=oS07F_sDfqnfF1vSs79skUR6A7Wios2SqrQ_PqqyFUM,8179
43
+ drf_to_mkdoc/utils/model_list_generator.py,sha256=xdZbZDz4Ujj7OI5k60xc6ZeFHQjQV8mALqiD65Odpqs,2578
44
+ drf_to_mkdoc/utils/schema.py,sha256=14Gs4OhKDlTlk6DRFjszWNA08U3WlExUrItFHwYIdB8,10101
45
+ drf_to_mkdoc/utils/ai_tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
+ drf_to_mkdoc/utils/ai_tools/enums.py,sha256=K39bJjHgXwNst2NL6z-5bztW3ZU5iCxg2b0KEodD6eM,238
47
+ drf_to_mkdoc/utils/ai_tools/exceptions.py,sha256=yFauOtuSRGRnQt41u2qCMvWEbN0eblgKwXuH-GX3Wbk,634
48
+ drf_to_mkdoc/utils/ai_tools/types.py,sha256=KZhE92sXbifEo0hx-DA4smKnIUgCmCQXvhUvYjX_3LY,2032
49
+ drf_to_mkdoc/utils/ai_tools/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
+ drf_to_mkdoc/utils/ai_tools/providers/base_provider.py,sha256=mSp5xeWkym4Fg2m0iIXaXGotSVSWWdfN6RfeI_8e2VY,4132
51
+ drf_to_mkdoc/utils/ai_tools/providers/gemini_provider.py,sha256=BJGwhy7rEsgV03catSmAq1Kx6kXAzCh_-5WoT3SCS94,2969
52
+ drf_to_mkdoc/utils/commons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
+ drf_to_mkdoc/utils/commons/code_extractor.py,sha256=a4SyfTCyh9KGB8-p9pi760E_l8ZoWA6yGhaBi6Zn6XA,665
54
+ drf_to_mkdoc/utils/commons/file_utils.py,sha256=pdjrNZ_oR664tIwKinhlHL3Oeo-pKwaI4vG_PQT-9R8,1182
55
+ drf_to_mkdoc/utils/commons/model_utils.py,sha256=IC2X-SchY448N_T6HA0iOkjuEkmRN92fnDHiaD95aR0,3033
56
+ drf_to_mkdoc/utils/commons/operation_utils.py,sha256=0nQqJYuXcmUeIgEg5mvRPGC9uwMKJFTpswV0L8UX35w,2691
57
+ drf_to_mkdoc/utils/commons/path_utils.py,sha256=Pi9g1xXDPsRzmn4kTeNSVtXG9v6n1h2ZphUgOCYAduw,2992
58
+ drf_to_mkdoc/utils/commons/schema_utils.py,sha256=0sw3zp0onsF3NVv4ImpHbXfypTA2yIIFMIId_D9NZfg,7837
44
59
  drf_to_mkdoc/utils/extractors/__init__.py,sha256=BvC8gKOPVI9gU1Piw0jRhKQ2ER5s1moAxgq7ZYkJWNI,86
45
- drf_to_mkdoc/utils/extractors/query_parameter_extractors.py,sha256=gnDlvF-bxkTp2NcEQf0EZZAfmdQzUpedhZ7N1LAYQzU,8432
60
+ drf_to_mkdoc/utils/extractors/query_parameter_extractors.py,sha256=5QY5_PGQ5XpXRL9ZLr0570ywPcygQ8ZxnlfnSCHhnF0,8540
46
61
  drf_to_mkdoc/utils/md_generators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
62
  drf_to_mkdoc/utils/md_generators/query_parameters_generators.py,sha256=N-XqZ_FUODSR5V4xM9oEA3aaIiNGNmNwpvrWbQTx6RI,2566
48
- drf_to_mkdoc-0.2.0.dist-info/licenses/LICENSE,sha256=3n9_ckIREsH8ogCxWW6dFsw_WfGcluG2mHcgl9i_UU0,1068
49
- drf_to_mkdoc-0.2.0.dist-info/METADATA,sha256=UL7fU0BVLOoteGIHaJJxpNj2rHw9KXG1-l1xtKXJRno,7037
50
- drf_to_mkdoc-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
51
- drf_to_mkdoc-0.2.0.dist-info/top_level.txt,sha256=ZzJecR6j_tvLZiubUBEgawHflozC4DQy9ooNf1yDJ3Q,13
52
- drf_to_mkdoc-0.2.0.dist-info/RECORD,,
63
+ drf_to_mkdoc-0.2.1.dist-info/licenses/LICENSE,sha256=3n9_ckIREsH8ogCxWW6dFsw_WfGcluG2mHcgl9i_UU0,1068
64
+ drf_to_mkdoc-0.2.1.dist-info/METADATA,sha256=ceVXnbJZXWmzEXhEKKhhwb64I9Nwqlds-4exCLYcvEE,7563
65
+ drf_to_mkdoc-0.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
66
+ drf_to_mkdoc-0.2.1.dist-info/top_level.txt,sha256=ZzJecR6j_tvLZiubUBEgawHflozC4DQy9ooNf1yDJ3Q,13
67
+ drf_to_mkdoc-0.2.1.dist-info/RECORD,,