drf-to-mkdoc 0.2.0__tar.gz → 0.2.1__tar.gz

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 (86) hide show
  1. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/PKG-INFO +16 -5
  2. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/README.md +15 -4
  3. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/conf/defaults.py +5 -0
  4. drf_to_mkdoc-0.2.1/drf_to_mkdoc/conf/settings.py +164 -0
  5. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/management/commands/build_docs.py +8 -7
  6. drf_to_mkdoc-0.2.1/drf_to_mkdoc/management/commands/build_endpoint_docs.py +69 -0
  7. drf_to_mkdoc-0.2.1/drf_to_mkdoc/management/commands/build_model_docs.py +50 -0
  8. drf_to_mkdoc-0.2.0/drf_to_mkdoc/management/commands/generate_model_docs.py → drf_to_mkdoc-0.2.1/drf_to_mkdoc/management/commands/extract_model_data.py +14 -19
  9. drf_to_mkdoc-0.2.1/drf_to_mkdoc/utils/ai_tools/enums.py +13 -0
  10. drf_to_mkdoc-0.2.1/drf_to_mkdoc/utils/ai_tools/exceptions.py +19 -0
  11. drf_to_mkdoc-0.2.1/drf_to_mkdoc/utils/ai_tools/providers/__init__.py +0 -0
  12. drf_to_mkdoc-0.2.1/drf_to_mkdoc/utils/ai_tools/providers/base_provider.py +123 -0
  13. drf_to_mkdoc-0.2.1/drf_to_mkdoc/utils/ai_tools/providers/gemini_provider.py +80 -0
  14. drf_to_mkdoc-0.2.1/drf_to_mkdoc/utils/ai_tools/types.py +81 -0
  15. drf_to_mkdoc-0.2.1/drf_to_mkdoc/utils/commons/__init__.py +0 -0
  16. drf_to_mkdoc-0.2.1/drf_to_mkdoc/utils/commons/code_extractor.py +22 -0
  17. drf_to_mkdoc-0.2.1/drf_to_mkdoc/utils/commons/file_utils.py +35 -0
  18. drf_to_mkdoc-0.2.1/drf_to_mkdoc/utils/commons/model_utils.py +83 -0
  19. drf_to_mkdoc-0.2.1/drf_to_mkdoc/utils/commons/operation_utils.py +83 -0
  20. drf_to_mkdoc-0.2.1/drf_to_mkdoc/utils/commons/path_utils.py +78 -0
  21. drf_to_mkdoc-0.2.1/drf_to_mkdoc/utils/commons/schema_utils.py +230 -0
  22. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/utils/endpoint_detail_generator.py +7 -34
  23. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/utils/endpoint_list_generator.py +1 -1
  24. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/utils/extractors/query_parameter_extractors.py +33 -30
  25. drf_to_mkdoc-0.2.1/drf_to_mkdoc/utils/md_generators/__init__.py +0 -0
  26. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/utils/model_detail_generator.py +21 -15
  27. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/utils/model_list_generator.py +25 -15
  28. drf_to_mkdoc-0.2.1/drf_to_mkdoc/utils/schema.py +259 -0
  29. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc.egg-info/PKG-INFO +16 -5
  30. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc.egg-info/SOURCES.txt +18 -3
  31. drf_to_mkdoc-0.2.0/drf_to_mkdoc/conf/settings.py +0 -50
  32. drf_to_mkdoc-0.2.0/drf_to_mkdoc/management/commands/generate_docs.py +0 -113
  33. drf_to_mkdoc-0.2.0/drf_to_mkdoc/utils/common.py +0 -353
  34. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/.github/workflows/publish.yaml +0 -0
  35. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/.pre-commit-config.yaml +0 -0
  36. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/CONTRIBUTING.md +0 -0
  37. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/LICENSE +0 -0
  38. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/MANIFEST.in +0 -0
  39. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/conf/__init__.py +0 -0
  40. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/conf/defaults.py +0 -0
  41. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/conf/settings.py +0 -0
  42. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/docs/customizing_endpoints.md +0 -0
  43. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/docs/mkdocs.yml +0 -0
  44. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/docs/serving_mkdocs_with_django.md +0 -0
  45. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/__init__.py +0 -0
  46. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/apps.py +0 -0
  47. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/conf/__init__.py +0 -0
  48. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/management/__init__.py +0 -0
  49. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/management/commands/__init__.py +0 -0
  50. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/management/commands/generate_doc_json.py +0 -0
  51. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/management/commands/update_doc_schema.py +0 -0
  52. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/static/drf-to-mkdoc/javascripts/endpoints-filter.js +0 -0
  53. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out-sidebar.js +0 -0
  54. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/accessibility.css +0 -0
  55. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/animations.css +0 -0
  56. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/badges.css +0 -0
  57. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/base.css +0 -0
  58. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/endpoint-content.css +0 -0
  59. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/endpoints-grid.css +0 -0
  60. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/filter-section.css +0 -0
  61. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/fixes.css +0 -0
  62. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/layout.css +0 -0
  63. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/loading.css +0 -0
  64. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/responsive.css +0 -0
  65. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/sections.css +0 -0
  66. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/stats.css +0 -0
  67. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/tags.css +0 -0
  68. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/theme-toggle.css +0 -0
  69. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/try-out-sidebar.css +0 -0
  70. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/variables.css +0 -0
  71. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/animations.css +0 -0
  72. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/base.css +0 -0
  73. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/model-cards.css +0 -0
  74. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/model-tables.css +0 -0
  75. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/responsive.css +0 -0
  76. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/variables.css +0 -0
  77. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/utils/__init__.py +0 -0
  78. {drf_to_mkdoc-0.2.0/drf_to_mkdoc/utils/md_generators → drf_to_mkdoc-0.2.1/drf_to_mkdoc/utils/ai_tools}/__init__.py +0 -0
  79. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/utils/extractors/__init__.py +0 -0
  80. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc/utils/md_generators/query_parameters_generators.py +0 -0
  81. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc.egg-info/dependency_links.txt +0 -0
  82. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc.egg-info/requires.txt +0 -0
  83. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/drf_to_mkdoc.egg-info/top_level.txt +0 -0
  84. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/pyproject.toml +0 -0
  85. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/setup.cfg +0 -0
  86. {drf_to_mkdoc-0.2.0 → drf_to_mkdoc-0.2.1}/setup.py +0 -0
@@ -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.
@@ -27,13 +27,14 @@ INSTALLED_APPS = [
27
27
 
28
28
  # Required for OpenAPI schema generation
29
29
  REST_FRAMEWORK = {
30
- 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
30
+ 'DEFAULT_SCHEMA_CLASS': 'drf_to_mkdoc.utils.schema.AutoSchema', # Use our custom AutoSchema
31
31
  }
32
32
 
33
33
  SPECTACULAR_SETTINGS = {
34
34
  'TITLE': 'Your API',
35
35
  'DESCRIPTION': 'Your API description',
36
36
  'VERSION': '1.0.0',
37
+
37
38
  }
38
39
 
39
40
  DRF_TO_MKDOC = {
@@ -61,10 +62,19 @@ DRF_TO_MKDOC = {
61
62
  python manage.py build_docs --settings=docs_settings
62
63
  ```
63
64
 
65
+ ## Available Commands
66
+
67
+ - `build_docs`: Build the complete documentation site with MkDocs
68
+ - `build_endpoint_docs`: Build endpoint documentation from OpenAPI schema
69
+ - `build_model_docs`: Build model documentation from model JSON data
70
+ - `extract_model_data`: Extract model data from Django model introspection and save as JSON
71
+ - `update_doc_schema`: Update the final schema by copying the documented schema
72
+
64
73
  ## What you get
65
74
 
66
75
  See a detailed overview of generated files in `docs/structure.md` and a feature breakdown in `docs/features.md`.
67
76
 
77
+
68
78
  ## How it works
69
79
 
70
80
  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.
@@ -105,8 +115,9 @@ drf-to-mkdoc/
105
115
  │ ├── management/
106
116
  │ │ └── commands/
107
117
  │ │ ├── build_docs.py # Build MkDocs site
108
- │ │ ├── generate_docs.py # Main documentation generator
109
- │ │ ├── generate_model_docs.py # Model documentation
118
+ │ │ ├── build_endpoint_docs.py # Build endpoint documentation
119
+ │ │ ├── build_model_docs.py # Build model documentation
120
+ │ │ ├── extract_model_data.py # Extract model data from Django
110
121
  │ │ └── update_doc_schema.py # Schema updates
111
122
  │ └── utils/
112
123
  │ ├── common.py # Shared utilities
@@ -173,4 +184,4 @@ your-project/
173
184
  ## Contributing
174
185
 
175
186
  See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed contribution guidelines.
176
- This will ensure that only the source configuration and scripts are versioned, while the generated documentation is excluded.
187
+ This will ensure that only the source configuration and scripts are versioned, while the generated documentation is excluded.
@@ -7,6 +7,11 @@ DEFAULTS = {
7
7
  "CUSTOM_SCHEMA_FILE": "docs/configs/custom_schema.json", # Path to custom schema file
8
8
  "PATH_PARAM_SUBSTITUTE_FUNCTION": None,
9
9
  "PATH_PARAM_SUBSTITUTE_MAPPING": {},
10
+ # AI documentation settings
11
+ "ENABLE_AI_DOCS": False,
12
+ "AI_CONFIG_DIR_NAME": "ai_code", # Directory name for AI-generated code files
13
+ "AI_OPERATION_MAP_FILE": "docs/configs/operation_map.json", # Path to operation map file
14
+ "SERIALIZERS_INHERITANCE_DEPTH": 1, # Maximum depth for class inheritance analysis
10
15
  # Django apps - required, no default
11
16
  "DJANGO_APPS": None, # List of Django app names to process
12
17
  }
@@ -0,0 +1,164 @@
1
+ from pathlib import Path
2
+ from typing import Any, ClassVar
3
+
4
+ from django.conf import settings
5
+
6
+ from drf_to_mkdoc.conf.defaults import DEFAULTS
7
+
8
+
9
+ class DRFToMkDocSettings:
10
+ required_settings: ClassVar[list[str]] = ["DJANGO_APPS"]
11
+ project_settings: ClassVar[dict[str, Any]] = {"PROJECT_NAME": "drf-to-mkdoc"}
12
+
13
+ settings_types: ClassVar[dict[str, type]] = {
14
+ "ENABLE_AI_DOCS": bool,
15
+ "AI_CONFIG_DIR_NAME": str,
16
+ "SERIALIZERS_INHERITANCE_DEPTH": int,
17
+ }
18
+
19
+ settings_ranges: ClassVar[dict[str, tuple[int, int]]] = {
20
+ "SERIALIZERS_INHERITANCE_DEPTH": (1, 3),
21
+ }
22
+
23
+ path_settings = {
24
+ "CONFIG_DIR",
25
+ "MODEL_DOCS_FILE",
26
+ "DOC_CONFIG_FILE",
27
+ "CUSTOM_SCHEMA_FILE",
28
+ "AI_OPERATION_MAP_FILE",
29
+ }
30
+
31
+ def __init__(self, user_settings_key="DRF_TO_MKDOC", defaults=None):
32
+ self.user_settings_key = user_settings_key
33
+ self._user_settings = getattr(settings, user_settings_key, {})
34
+ self.defaults = defaults or {}
35
+
36
+ def _validate_type(self, key: str, value: Any) -> None:
37
+ """Validate the type of setting value."""
38
+ if key in self.settings_types:
39
+ expected_type = self.settings_types[key]
40
+ if not isinstance(value, expected_type):
41
+ raise TypeError(
42
+ f"DRF_TO_MKDOC setting '{key}' must be of type {expected_type.__name__}, "
43
+ f"got {type(value).__name__} instead."
44
+ )
45
+
46
+ def _validate_range(self, key: str, value: Any) -> None:
47
+ """Validate the range of a setting value."""
48
+ if key in self.settings_ranges:
49
+ min_val, max_val = self.settings_ranges[key]
50
+ if not min_val <= value <= max_val:
51
+ raise ValueError(
52
+ f"DRF_TO_MKDOC setting '{key}' must be between {min_val} and {max_val}, "
53
+ f"got {value} instead."
54
+ )
55
+
56
+ def _validate_required(self, key: str, value: Any) -> None:
57
+ """Validate if a required setting is configured."""
58
+ if value is None and key in self.required_settings:
59
+ raise ValueError(
60
+ f"DRF_TO_MKDOC setting '{key}' is required but not configured. "
61
+ f"Please add it to your Django settings under {self.user_settings_key}."
62
+ )
63
+
64
+ def _validate_dir(self, key: str, value: str) -> None:
65
+ if key not in self.path_settings or not isinstance(value, str):
66
+ return
67
+
68
+ if not value.strip():
69
+ raise ValueError(
70
+ f"DRF_TO_MKDOC path setting '{key}' cannot be empty or contain only whitespace."
71
+ )
72
+
73
+ dangerous_components = {"..", "~", "/", "\\"}
74
+ path_parts = Path(value).parts
75
+ for part in path_parts:
76
+ if part in dangerous_components or part.startswith("."):
77
+ raise ValueError(
78
+ f"DRF_TO_MKDOC path setting '{key}' contains unsafe path component '{part}'. "
79
+ f"Directory names should be simple names without separators or relative path components."
80
+ )
81
+
82
+ if Path(value).is_absolute():
83
+ raise ValueError(
84
+ f"DRF_TO_MKDOC path setting '{key}' cannot be an absolute path. "
85
+ f"Use relative directory names only."
86
+ )
87
+
88
+ reserved_names = {
89
+ "CON",
90
+ "PRN",
91
+ "AUX",
92
+ "NUL",
93
+ "COM1",
94
+ "COM2",
95
+ "COM3",
96
+ "COM4",
97
+ "COM5",
98
+ "COM6",
99
+ "COM7",
100
+ "COM8",
101
+ "COM9",
102
+ "LPT1",
103
+ "LPT2",
104
+ "LPT3",
105
+ "LPT4",
106
+ "LPT5",
107
+ "LPT6",
108
+ "LPT7",
109
+ "LPT8",
110
+ "LPT9",
111
+ }
112
+ if value.upper() in reserved_names:
113
+ raise ValueError(
114
+ f"DRF_TO_MKDOC path setting '{key}' uses a reserved system name '{value}'. "
115
+ f"Please choose a different name."
116
+ )
117
+
118
+ invalid_chars = '<>:"|?*'
119
+ if any(char in value for char in invalid_chars):
120
+ raise ValueError(
121
+ f"DRF_TO_MKDOC path setting '{key}' contains invalid characters. "
122
+ f"Avoid using: {invalid_chars}"
123
+ )
124
+
125
+ def get(self, key):
126
+ if key in self.project_settings:
127
+ return self.project_settings[key]
128
+
129
+ # User-provided settings take precedence
130
+ if key in self._user_settings:
131
+ value = self._user_settings[key]
132
+ else:
133
+ value = self.defaults.get(key, None)
134
+
135
+ # Run all validations
136
+ self._validate_required(key, value)
137
+ self._validate_type(key, value)
138
+ self._validate_range(key, value)
139
+ self._validate_dir(key, value)
140
+
141
+ if value is None:
142
+ raise AttributeError(f"Invalid DRF_TO_MKDOC setting: '{key}'")
143
+ return value
144
+
145
+ def __getattr__(self, key):
146
+ return self.get(key)
147
+
148
+ def validate_required_settings(self):
149
+ missing_settings = []
150
+
151
+ for setting in self.required_settings:
152
+ try:
153
+ self.get(setting)
154
+ except (ValueError, AttributeError):
155
+ missing_settings.append(setting)
156
+
157
+ if missing_settings:
158
+ raise ValueError(
159
+ f"Missing required settings: {', '.join(missing_settings)}. "
160
+ f"Please configure these in your Django settings under {self.user_settings_key}."
161
+ )
162
+
163
+
164
+ drf_to_mkdoc_settings = DRFToMkDocSettings(defaults=DEFAULTS)
@@ -34,15 +34,16 @@ class Command(BaseCommand):
34
34
  )
35
35
 
36
36
  try:
37
- # Generate the model documentation JSON first
38
- self.stdout.write("Generating model documentation...")
39
- call_command("generate_model_docs", "--pretty")
40
- self.stdout.write(self.style.SUCCESS("Model documentation generated."))
37
+ # Extract model data from Django models
38
+ self.stdout.write("Extracting model data...")
39
+ call_command("extract_model_data", "--pretty")
40
+ self.stdout.write(self.style.SUCCESS("Model data extracted."))
41
41
 
42
42
  # Generate the documentation content
43
- self.stdout.write("Generating documentation content...")
44
- call_command("generate_docs")
45
- self.stdout.write(self.style.SUCCESS("Documentation content generated."))
43
+ self.stdout.write("Building documentation content...")
44
+ call_command("build_model_docs")
45
+ call_command("build_endpoint_docs")
46
+ self.stdout.write(self.style.SUCCESS("Documentation content built."))
46
47
 
47
48
  # Build the MkDocs site
48
49
  self.stdout.write("Building MkDocs site...")
@@ -0,0 +1,69 @@
1
+ from pathlib import Path
2
+
3
+ from django.core.management.base import BaseCommand
4
+
5
+ from drf_to_mkdoc.conf.settings import drf_to_mkdoc_settings
6
+ from drf_to_mkdoc.utils.commons.schema_utils import get_schema
7
+ from drf_to_mkdoc.utils.endpoint_detail_generator import (
8
+ generate_endpoint_files,
9
+ parse_endpoints_from_schema,
10
+ )
11
+ from drf_to_mkdoc.utils.endpoint_list_generator import create_endpoints_index
12
+
13
+
14
+ class Command(BaseCommand):
15
+ help = "Build endpoint documentation from OpenAPI schema"
16
+
17
+ def handle(self, *args, **options):
18
+ self.stdout.write(
19
+ self.style.SUCCESS("🚀 Starting endpoint documentation generation...")
20
+ )
21
+
22
+ docs_dir = self._setup_docs_directory()
23
+ schema_data = self._load_schema_data()
24
+
25
+ if schema_data:
26
+ self._generate_endpoints_documentation(schema_data, docs_dir)
27
+ self.stdout.write(
28
+ self.style.SUCCESS("✅ Endpoint documentation generation complete!")
29
+ )
30
+ else:
31
+ self.stdout.write(self.style.ERROR("❌ Failed to generate endpoint documentation."))
32
+
33
+ def _setup_docs_directory(self):
34
+ docs_dir = Path(drf_to_mkdoc_settings.DOCS_DIR)
35
+ docs_dir.mkdir(parents=True, exist_ok=True)
36
+ return docs_dir
37
+
38
+ def _load_schema_data(self):
39
+ try:
40
+ schema = get_schema()
41
+ except Exception as e:
42
+ self.stdout.write(self.style.ERROR(f"❌ Failed to load OpenAPI schema: {e}"))
43
+ return {}
44
+ if not schema:
45
+ self.stdout.write(self.style.ERROR("❌ Failed to load OpenAPI schema"))
46
+ return {}
47
+
48
+ paths = schema.get("paths", {})
49
+ components = schema.get("components", {})
50
+
51
+ self.stdout.write(f"📊 Loaded {len(paths)} API paths")
52
+
53
+ return {"paths": paths, "components": components}
54
+
55
+ def _generate_endpoints_documentation(self, schema_data, docs_dir):
56
+ self.stdout.write("🔗 Generating endpoint documentation...")
57
+
58
+ paths = schema_data["paths"]
59
+ components = schema_data["components"]
60
+
61
+ endpoints_by_app = parse_endpoints_from_schema(paths)
62
+ total_endpoints = generate_endpoint_files(endpoints_by_app, components)
63
+ create_endpoints_index(endpoints_by_app, docs_dir)
64
+
65
+ self.stdout.write(
66
+ self.style.SUCCESS(
67
+ f"✅ Generated {total_endpoints} endpoint files with Django view introspection"
68
+ )
69
+ )
@@ -0,0 +1,50 @@
1
+ from pathlib import Path
2
+
3
+ from django.core.management.base import BaseCommand
4
+
5
+ from drf_to_mkdoc.conf.settings import drf_to_mkdoc_settings
6
+ from drf_to_mkdoc.utils.commons.file_utils import load_json_data
7
+ from drf_to_mkdoc.utils.model_detail_generator import generate_model_docs
8
+ from drf_to_mkdoc.utils.model_list_generator import create_models_index
9
+
10
+
11
+ class Command(BaseCommand):
12
+ help = "Build model documentation from model JSON data"
13
+
14
+ def handle(self, *args, **options):
15
+ self.stdout.write(self.style.SUCCESS("🚀 Starting model documentation generation..."))
16
+
17
+ docs_dir = self._setup_docs_directory()
18
+ models_data = self._load_models_data()
19
+
20
+ if models_data:
21
+ self._generate_models_documentation(models_data, docs_dir)
22
+ self.stdout.write(self.style.SUCCESS("✅ Model documentation generation complete!"))
23
+ else:
24
+ self.stdout.write(self.style.ERROR("❌ Failed to generate model documentation."))
25
+
26
+ def _setup_docs_directory(self):
27
+ docs_dir = Path(drf_to_mkdoc_settings.DOCS_DIR)
28
+ docs_dir.mkdir(parents=True, exist_ok=True)
29
+ return docs_dir
30
+
31
+ def _load_models_data(self):
32
+ models_data = load_json_data(
33
+ drf_to_mkdoc_settings.MODEL_DOCS_FILE, raise_not_found=False
34
+ )
35
+
36
+ if not models_data:
37
+ self.stdout.write(self.style.WARNING("⚠️ No model data found"))
38
+
39
+ return models_data
40
+
41
+ def _generate_models_documentation(self, models_data, docs_dir):
42
+ self.stdout.write("📋 Generating model documentation...")
43
+
44
+ try:
45
+ generate_model_docs(models_data)
46
+ create_models_index(models_data, docs_dir)
47
+ self.stdout.write(self.style.SUCCESS("✅ Model documentation generated"))
48
+ except Exception as e:
49
+ self.stdout.write(self.style.WARNING(f"⚠️ Failed to generate model docs: {e}"))
50
+ raise
@@ -1,6 +1,7 @@
1
1
  import inspect
2
2
  import json
3
3
  import re
4
+ from collections import defaultdict
4
5
  from pathlib import Path
5
6
 
6
7
  from django.apps import apps
@@ -11,7 +12,7 @@ from drf_to_mkdoc.conf.settings import drf_to_mkdoc_settings
11
12
 
12
13
 
13
14
  class Command(BaseCommand):
14
- help = "Generate model documentation JSON from Django model introspection"
15
+ help = "Extract model data from Django model introspection and save as JSON"
15
16
 
16
17
  def add_arguments(self, parser):
17
18
  parser.add_argument(
@@ -38,33 +39,26 @@ class Command(BaseCommand):
38
39
 
39
40
  model_docs = self.generate_model_documentation(exclude_apps)
40
41
 
41
- json_data = {
42
- "models": model_docs,
43
- "stats": {
44
- "total_models": len(model_docs),
45
- "total_apps": len({model["app_label"] for model in model_docs.values()}),
46
- },
47
- }
48
-
49
42
  output_path = Path(output_file)
50
43
  output_path.parent.mkdir(parents=True, exist_ok=True)
51
44
  with output_path.open("w", encoding="utf-8") as f:
45
+ payload = dict(model_docs)
52
46
  if pretty:
53
- json.dump(json_data, f, indent=2, ensure_ascii=False, default=str)
47
+ json.dump(payload, f, ensure_ascii=False, sort_keys=True, default=str, indent=2)
54
48
  else:
55
- json.dump(json_data, f, ensure_ascii=False, default=str)
49
+ json.dump(payload, f, ensure_ascii=False, sort_keys=True, default=str)
56
50
 
57
51
  self.stdout.write(
58
52
  self.style.SUCCESS(f"✅ Generated model documentation: {output_path.absolute()}")
59
53
  )
60
- self.stdout.write(f"📊 Total models: {len(model_docs)}")
61
54
  self.stdout.write(
62
- f"📦 Total apps: {len({model['app_label'] for model in model_docs.values()})}"
55
+ f"📊 Total models: {sum([len(model_docs[app_label]) for app_label in model_docs])}"
63
56
  )
57
+ self.stdout.write(f"📦 Total apps: {len(model_docs)}")
64
58
 
65
59
  def generate_model_documentation(self, exclude_apps):
66
60
  """Generate documentation for all Django models"""
67
- model_docs = {}
61
+ model_docs = defaultdict(dict)
68
62
 
69
63
  for app_config in apps.get_app_configs():
70
64
  app_label = app_config.label
@@ -78,13 +72,11 @@ class Command(BaseCommand):
78
72
 
79
73
  for model in app_config.get_models():
80
74
  model_name = model.__name__
81
- model_key = f"{app_label}.{model_name}"
82
-
83
75
  self.stdout.write(f" 📋 Processing model: {model_name}")
84
76
 
85
- model_docs[model_key] = self.introspect_model(model, app_label)
77
+ model_docs[app_label][model_name] = self.introspect_model(model, app_label)
86
78
 
87
- return model_docs
79
+ return {app_label: dict(models) for app_label, models in model_docs.items()}
88
80
 
89
81
  def introspect_model(self, model, app_label):
90
82
  """Introspect a single Django model"""
@@ -154,6 +146,9 @@ class Command(BaseCommand):
154
146
  "type": field.__class__.__name__,
155
147
  "related_model": related_model_label,
156
148
  "related_name": getattr(field, "related_name", None),
149
+ "app_label": field.related_model._meta.app_label,
150
+ "table_name": field.related_model._meta.db_table,
151
+ "verbose_name": field.related_model._meta.verbose_name,
157
152
  "on_delete": self.get_on_delete_name(field),
158
153
  "null": getattr(field, "null", False),
159
154
  "blank": getattr(field, "blank", False),
@@ -286,7 +281,7 @@ class Command(BaseCommand):
286
281
  if display_method_match:
287
282
  field_name = display_method_match.group(1)
288
283
  if field_name in model_field_names:
289
- # Exclude if the field doesn't exist in the model
284
+ # Exclude built-in get_<field>_display for fields present on the model
290
285
  continue
291
286
 
292
287
  method = getattr(model, attr_name)
@@ -0,0 +1,13 @@
1
+ from enum import Enum
2
+
3
+
4
+ class MessageRole(Enum):
5
+ SYSTEM = "system"
6
+ USER = "user"
7
+ ASSISTANT = "assistant"
8
+ FUNCTION = "function"
9
+ TOOL = "tool"
10
+ MODEL = "model"
11
+
12
+ def __str__(self) -> str:
13
+ return self.value
@@ -0,0 +1,19 @@
1
+ class AIProviderError(Exception):
2
+ """Base exception for AI provider errors"""
3
+
4
+ def __init__(self, message: str, provider: str | None = None, model: str | None = None):
5
+ self.provider = provider
6
+ self.model = model
7
+ super().__init__(message)
8
+
9
+ def __str__(self) -> str:
10
+ base = super().__str__()
11
+ meta = ", ".join(
12
+ part
13
+ for part in (
14
+ f"provider={self.provider}" if self.provider else None,
15
+ f"model={self.model}" if self.model else None,
16
+ )
17
+ if part
18
+ )
19
+ return f"{base} ({meta})" if meta else base