sphinx-mkdocs-migrate 0.0.1.dev0__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.
Files changed (39) hide show
  1. sphinx_mkdocs_migrate/__init__.py +8 -0
  2. sphinx_mkdocs_migrate/analyzer/__init__.py +17 -0
  3. sphinx_mkdocs_migrate/analyzer/ci.py +223 -0
  4. sphinx_mkdocs_migrate/analyzer/dependencies.py +134 -0
  5. sphinx_mkdocs_migrate/analyzer/markdown.py +148 -0
  6. sphinx_mkdocs_migrate/analyzer/mkdocs.py +263 -0
  7. sphinx_mkdocs_migrate/analyzer/models.py +348 -0
  8. sphinx_mkdocs_migrate/analyzer/navigation.py +108 -0
  9. sphinx_mkdocs_migrate/analyzer/project.py +511 -0
  10. sphinx_mkdocs_migrate/cli.py +507 -0
  11. sphinx_mkdocs_migrate/parsing/doc_ir.py +533 -0
  12. sphinx_mkdocs_migrate/parsing/flow_extractor.py +457 -0
  13. sphinx_mkdocs_migrate/parsing/html_flow_parser.py +349 -0
  14. sphinx_mkdocs_migrate/parsing/markdown.py +22 -0
  15. sphinx_mkdocs_migrate/parsing/markdown_ir.py +49 -0
  16. sphinx_mkdocs_migrate/parsing/markdown_it_adapter.py +496 -0
  17. sphinx_mkdocs_migrate/parsing/requirements.py +155 -0
  18. sphinx_mkdocs_migrate/planner/accountability.py +111 -0
  19. sphinx_mkdocs_migrate/planner/ci.py +142 -0
  20. sphinx_mkdocs_migrate/planner/conf_builder.py +183 -0
  21. sphinx_mkdocs_migrate/planner/models.py +379 -0
  22. sphinx_mkdocs_migrate/planner/planner.py +1867 -0
  23. sphinx_mkdocs_migrate/planner/policy.py +474 -0
  24. sphinx_mkdocs_migrate/planner/theme_constants.py +70 -0
  25. sphinx_mkdocs_migrate/planner/toctree.py +158 -0
  26. sphinx_mkdocs_migrate/py.typed +1 -0
  27. sphinx_mkdocs_migrate/rules/catalog.py +154 -0
  28. sphinx_mkdocs_migrate/rules/engine.py +94 -0
  29. sphinx_mkdocs_migrate/rules/models.py +176 -0
  30. sphinx_mkdocs_migrate/transformer/engine.py +897 -0
  31. sphinx_mkdocs_migrate/transformer/models.py +59 -0
  32. sphinx_mkdocs_migrate/transformer/myst_transformer.py +393 -0
  33. sphinx_mkdocs_migrate/validator/models.py +40 -0
  34. sphinx_mkdocs_migrate/validator/verifier.py +377 -0
  35. sphinx_mkdocs_migrate-0.0.1.dev0.dist-info/METADATA +199 -0
  36. sphinx_mkdocs_migrate-0.0.1.dev0.dist-info/RECORD +39 -0
  37. sphinx_mkdocs_migrate-0.0.1.dev0.dist-info/WHEEL +4 -0
  38. sphinx_mkdocs_migrate-0.0.1.dev0.dist-info/entry_points.txt +2 -0
  39. sphinx_mkdocs_migrate-0.0.1.dev0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,533 @@
1
+ """Semantic Documentation Intermediate Representation (IR) Models.
2
+
3
+ Schema Version: 1.0.0
4
+ Invariants:
5
+ 1. Documentation IR is the migration authority.
6
+ 2. Document flow is an ordered, heterogeneous, strongly-typed sequence with provenance (SourceSpan).
7
+ 3. Discriminated unions prevent element_type and content drift.
8
+ 4. Rendered HTML is verification evidence, not the migration source of truth.
9
+ """
10
+
11
+ import hashlib
12
+ import json
13
+ from enum import Enum
14
+ from typing import List, Dict, Any, Optional, Union, Set
15
+ from pydantic import BaseModel, Field
16
+
17
+ DOCUMENTATION_IR_SCHEMA_VERSION = "1.0.0"
18
+
19
+ # --- 1. Provenance & Source Spans ---
20
+
21
+
22
+ class SourceSpan(BaseModel):
23
+ file: str
24
+ start_line: Optional[int] = None
25
+ end_line: Optional[int] = None
26
+ start_column: Optional[int] = None
27
+ end_column: Optional[int] = None
28
+
29
+
30
+ # --- 2. API Documentation Request Models ---
31
+
32
+
33
+ class ApiObjectKind(str, Enum):
34
+ MODULE = "module"
35
+ PACKAGE = "package"
36
+ CLASS = "class"
37
+ FUNCTION = "function"
38
+ METHOD = "method"
39
+ ATTRIBUTE = "attribute"
40
+ EXCEPTION = "exception"
41
+ UNKNOWN = "unknown"
42
+
43
+
44
+ class MemberSelection(str, Enum):
45
+ NOT_SPECIFIED = "NOT_SPECIFIED"
46
+ ALL_PUBLIC = "ALL_PUBLIC"
47
+ EXPLICIT = "EXPLICIT"
48
+ DOCSTRING_ONLY = "DOCSTRING_ONLY"
49
+ NONE = "NONE"
50
+
51
+
52
+ class SummaryMode(str, Enum):
53
+ NOT_REQUESTED = "NOT_REQUESTED"
54
+ EXPLICIT = "EXPLICIT"
55
+ HANDLER_DEFAULT = "HANDLER_DEFAULT"
56
+ INFERRED = "INFERRED"
57
+
58
+
59
+ class ResolutionStatus(str, Enum):
60
+ RESOLVED = "RESOLVED"
61
+ UNRESOLVED = "UNRESOLVED"
62
+ AMBIGUOUS = "AMBIGUOUS"
63
+
64
+
65
+ class ApiDocumentationRequest(BaseModel):
66
+ construct_id: str
67
+ source_span: SourceSpan
68
+ object_path: str
69
+ object_kind: ApiObjectKind = ApiObjectKind.UNKNOWN
70
+ handler: str = "mkdocstrings.python"
71
+ raw_options: Dict[str, Any] = Field(default_factory=dict)
72
+ normalized_options: Dict[str, Any] = Field(default_factory=dict)
73
+ include_docstring: bool = True
74
+ member_selection: MemberSelection = MemberSelection.NOT_SPECIFIED
75
+ explicit_members: List[str] = Field(default_factory=list)
76
+ summary_mode: SummaryMode = SummaryMode.NOT_REQUESTED
77
+ resolution_status: ResolutionStatus = ResolutionStatus.RESOLVED
78
+ resolved_file: Optional[str] = None
79
+ resolved_symbol: Optional[str] = None
80
+ inheritance: List[str] = Field(default_factory=list)
81
+ declared_members: List[str] = Field(default_factory=list)
82
+
83
+
84
+ # --- 3. Strongly-Typed Content Elements ---
85
+
86
+
87
+ class InlineElementKind(str, Enum):
88
+ TEXT = "TEXT"
89
+ CODE = "CODE"
90
+ EMPHASIS = "EMPHASIS"
91
+ STRONG = "STRONG"
92
+ LINK = "LINK"
93
+
94
+
95
+ class InlineElement(BaseModel):
96
+ kind: InlineElementKind
97
+ text: str
98
+ target: Optional[str] = None
99
+
100
+
101
+ class HeadingElement(BaseModel):
102
+ level: int
103
+ text: str
104
+ anchor_id: Optional[str] = None
105
+ inlines: List[InlineElement] = Field(default_factory=list)
106
+
107
+
108
+ class ParagraphElement(BaseModel):
109
+ text: str
110
+ inlines: List[InlineElement] = Field(default_factory=list)
111
+
112
+
113
+ class ListItemElement(BaseModel):
114
+ text: str
115
+ inlines: List[InlineElement] = Field(default_factory=list)
116
+ children: List["ListItemElement"] = Field(default_factory=list)
117
+
118
+
119
+ class ListElement(BaseModel):
120
+ ordered: bool = False
121
+ items: List[ListItemElement] = Field(default_factory=list)
122
+
123
+
124
+ class TableCellElement(BaseModel):
125
+ text: str
126
+ inlines: List[InlineElement] = Field(default_factory=list)
127
+ colspan: int = 1
128
+ rowspan: int = 1
129
+
130
+
131
+ class TableElement(BaseModel):
132
+ caption: Optional[str] = None
133
+ headers: List[TableCellElement] = Field(default_factory=list)
134
+ rows: List[List[TableCellElement]] = Field(default_factory=list)
135
+
136
+
137
+ class CodeBlockElement(BaseModel):
138
+ language: Optional[str] = None
139
+ code: str
140
+ title: Optional[str] = None
141
+ linenums: bool = False
142
+ highlight_lines: List[int] = Field(default_factory=list)
143
+
144
+
145
+ class AdmonitionElement(BaseModel):
146
+ kind: str
147
+ title: Optional[str] = None
148
+ content_text: str
149
+ collapsible: bool = False
150
+ inlines: List[InlineElement] = Field(default_factory=list)
151
+
152
+
153
+ class ImageElement(BaseModel):
154
+ src: str
155
+ alt: Optional[str] = None
156
+ title: Optional[str] = None
157
+ resolved_path: Optional[str] = None
158
+
159
+
160
+ class LinkBlockElement(BaseModel):
161
+ text: str
162
+ target: str
163
+ title: Optional[str] = None
164
+ is_internal: bool = False
165
+
166
+
167
+ class SnippetElement(BaseModel):
168
+ snippet_path: str
169
+ resolved_path: Optional[str] = None
170
+ raw_content: Optional[str] = None
171
+
172
+
173
+ class RawHtmlElement(BaseModel):
174
+ raw_html: str
175
+
176
+
177
+ class UnknownElement(BaseModel):
178
+ tag_or_type: str
179
+ raw_content: str
180
+ rationale: str
181
+
182
+
183
+ # --- 4. Discriminated Union for Document Elements ---
184
+
185
+ DocumentElementContent = Union[
186
+ HeadingElement,
187
+ ParagraphElement,
188
+ ListElement,
189
+ TableElement,
190
+ CodeBlockElement,
191
+ AdmonitionElement,
192
+ ImageElement,
193
+ LinkBlockElement,
194
+ SnippetElement,
195
+ ApiDocumentationRequest,
196
+ RawHtmlElement,
197
+ UnknownElement,
198
+ ]
199
+
200
+
201
+ class DocumentElementType(str, Enum):
202
+ HEADING = "HEADING"
203
+ PARAGRAPH = "PARAGRAPH"
204
+ LIST = "LIST"
205
+ TABLE = "TABLE"
206
+ CODE_BLOCK = "CODE_BLOCK"
207
+ ADMONITION = "ADMONITION"
208
+ IMAGE = "IMAGE"
209
+ LINK_BLOCK = "LINK_BLOCK"
210
+ SNIPPET = "SNIPPET"
211
+ API_REQUEST = "API_REQUEST"
212
+ RAW_HTML = "RAW_HTML"
213
+ UNKNOWN = "UNKNOWN"
214
+
215
+
216
+ class DocumentElement(BaseModel):
217
+ construct_id: str
218
+ element_type: DocumentElementType
219
+ source_order_index: int
220
+ source_span: SourceSpan
221
+ content: DocumentElementContent
222
+
223
+
224
+ class DocumentFlowSpec(BaseModel):
225
+ source_file: str
226
+ elements: List[DocumentElement] = Field(default_factory=list)
227
+
228
+
229
+ # --- 5. Relationships, Links, & Navigation Graph ---
230
+
231
+
232
+ class DocumentationLink(BaseModel):
233
+ source_construct_id: str
234
+ target: str
235
+ fragment: Optional[str] = None
236
+ link_kind: str # internal_page, internal_anchor, external_url, asset
237
+ resolved: bool = False
238
+
239
+
240
+ class AssetReference(BaseModel):
241
+ source_construct_id: str
242
+ source_path: str
243
+ resolved_path: Optional[str] = None
244
+ asset_kind: str
245
+ exists: bool = False
246
+
247
+
248
+ class DocumentationPage(BaseModel):
249
+ source_file: str
250
+ logical_route: str
251
+ title: Optional[str] = None
252
+ flow: DocumentFlowSpec
253
+ outgoing_links: List[DocumentationLink] = Field(default_factory=list)
254
+ referenced_assets: List[AssetReference] = Field(default_factory=list)
255
+
256
+
257
+ class NavigationNode(BaseModel):
258
+ construct_id: str
259
+ label: str
260
+ page_route: Optional[str] = None
261
+ children: List["NavigationNode"] = Field(default_factory=list)
262
+ order_index: int = 0
263
+ hidden: bool = False
264
+
265
+
266
+ class GeneratedPipeline(BaseModel):
267
+ construct_id: str
268
+ source_plugin: str
269
+ source_configuration: Dict[str, Any] = Field(default_factory=dict)
270
+ inputs: List[str] = Field(default_factory=list)
271
+ generated_outputs: List[str] = Field(default_factory=list)
272
+ navigation_effects: List[str] = Field(default_factory=list)
273
+ generation_strategy: str
274
+ verification_status: str
275
+
276
+
277
+ # --- 6. Generic Documentation Build Graph Models ---
278
+
279
+
280
+ class BuildStageType(str, Enum):
281
+ SOURCE_PREPROCESSING = "SOURCE_PREPROCESSING"
282
+ GENERATED_SOURCE_VARIANT = "GENERATED_SOURCE_VARIANT"
283
+ DATA_DRIVEN_ARTIFACT = "DATA_DRIVEN_ARTIFACT"
284
+ LOCALE_OVERLAY = "LOCALE_OVERLAY"
285
+ CONFIG_SYNTHESIS = "CONFIG_SYNTHESIS"
286
+ DOCUMENTATION_RENDERER = "DOCUMENTATION_RENDERER"
287
+ SITE_ASSEMBLY = "SITE_ASSEMBLY"
288
+ DEPLOYMENT_ASSEMBLY = "DEPLOYMENT_ASSEMBLY"
289
+ ARTIFACT_COPY = "ARTIFACT_COPY"
290
+ VALIDATION = "VALIDATION"
291
+ SIDE_OUTPUT = "SIDE_OUTPUT"
292
+
293
+
294
+ class ArtifactScope(str, Enum):
295
+ GLOBAL = "GLOBAL"
296
+ LOCALE = "LOCALE"
297
+ DOCUMENT = "DOCUMENT"
298
+
299
+
300
+ class ArtifactConsumerKind(str, Enum):
301
+ SITE_ARTIFACT = "SITE_ARTIFACT"
302
+ REPOSITORY_ARTIFACT = "REPOSITORY_ARTIFACT"
303
+ DEPLOYMENT_ARTIFACT = "DEPLOYMENT_ARTIFACT"
304
+ TEST_ARTIFACT = "TEST_ARTIFACT"
305
+ TOOLING_ARTIFACT = "TOOLING_ARTIFACT"
306
+
307
+
308
+ class BuildArtifact(BaseModel):
309
+ artifact_id: str
310
+ path: str
311
+ logical_role: Optional[str] = (
312
+ None # e.g., "sponsor_banner_partial", "python310_syntax_variant"
313
+ )
314
+ materialized_path: Optional[str] = (
315
+ None # e.g., "docs/en/overrides/partials/banner-sponsors.html"
316
+ )
317
+ artifact_kind: str # source_file, generated_variant, data_partial, config_artifact, html_page, asset
318
+ producer_stage: str
319
+ consumer_stages: List[str] = Field(default_factory=list)
320
+ external_consumers: List[str] = Field(
321
+ default_factory=list
322
+ ) # e.g., ["documentation_source_tree", "test_suite"]
323
+ consumer_kinds: Set[ArtifactConsumerKind] = Field(
324
+ default_factory=lambda: {ArtifactConsumerKind.SITE_ARTIFACT}
325
+ )
326
+ scope: ArtifactScope = ArtifactScope.GLOBAL
327
+ locale: Optional[str] = None
328
+ is_intermediate: bool = False
329
+ provenance_source: Optional[str] = None
330
+
331
+
332
+ class StageWorkflowKind(str, Enum):
333
+ PREPARATION_WORKFLOW = "PREPARATION_WORKFLOW" # External / static preparation step (e.g. generate-docs-src-versions)
334
+ BUILD_PIPELINE = "BUILD_PIPELINE" # Core build DAG executed during site compilation
335
+ MAINTENANCE_WORKFLOW = "MAINTENANCE_WORKFLOW" # Auxiliary scripts (e.g. notify_translations, translation_fixer)
336
+
337
+
338
+ class DependencyJustificationKind(str, Enum):
339
+ ARTIFACT_DEPENDENCY = "ARTIFACT_DEPENDENCY"
340
+ EXTERNAL_DEPENDENCY = "EXTERNAL_DEPENDENCY"
341
+ CONTROL_DEPENDENCY = "CONTROL_DEPENDENCY"
342
+
343
+
344
+ class StageDependency(BaseModel):
345
+ antecedent_stage_id: str
346
+ justification_kind: DependencyJustificationKind
347
+ artifact_id: Optional[str] = None
348
+ description: Optional[str] = None
349
+
350
+
351
+ class BuildCondition(BaseModel):
352
+ condition_id: str
353
+ expression: str # e.g., "locale != 'en'", "is_fallback == True"
354
+ description: str
355
+
356
+
357
+ class BuildStage(BaseModel):
358
+ stage_id: str
359
+ stage_type: BuildStageType
360
+ workflow_kind: StageWorkflowKind = StageWorkflowKind.BUILD_PIPELINE
361
+ scope: ArtifactScope = ArtifactScope.GLOBAL
362
+ locale: Optional[str] = None
363
+ inputs: List[str] = Field(default_factory=list)
364
+ outputs: List[str] = Field(default_factory=list)
365
+ depends_on: List[str] = Field(
366
+ default_factory=list
367
+ ) # stage_ids that must complete before this
368
+ stage_dependencies: List[StageDependency] = Field(default_factory=list)
369
+ conditions: List[BuildCondition] = Field(default_factory=list)
370
+ strategy: str
371
+ consumer_kinds: Set[ArtifactConsumerKind] = Field(
372
+ default_factory=lambda: {ArtifactConsumerKind.SITE_ARTIFACT}
373
+ )
374
+ source_provenance_symbol: Optional[str] = None # e.g., "scripts/docs.py:build_all"
375
+ rationale: str
376
+
377
+
378
+ class LocaleDocumentSpec(BaseModel):
379
+ logical_route: str
380
+ locale: str
381
+ canonical_source: str
382
+ localized_source: Optional[str] = None
383
+ effective_source: str
384
+ is_fallback: bool = False
385
+ translation_notice_required: bool = False
386
+ translation_status: str = "CANONICAL" # CANONICAL, TRANSLATED, FALLBACK, EXCLUDED
387
+
388
+
389
+ class LocaleOverlaySpec(BaseModel):
390
+ locale: str
391
+ is_canonical: bool = False
392
+ canonical_source_dir: str
393
+ localized_source_dir: Optional[str] = None
394
+ fallback_locale: Optional[str] = "en"
395
+ inject_missing_notice: bool = True
396
+ excluded_paths: List[str] = Field(default_factory=list)
397
+ documents: Dict[str, LocaleDocumentSpec] = Field(default_factory=dict)
398
+
399
+
400
+ class DocumentationBuildGraph(BaseModel):
401
+ schema_version: str = DOCUMENTATION_IR_SCHEMA_VERSION
402
+ stages: Dict[str, BuildStage] = Field(default_factory=dict)
403
+ artifacts: Dict[str, BuildArtifact] = Field(default_factory=dict)
404
+ locales: Dict[str, LocaleOverlaySpec] = Field(default_factory=dict)
405
+
406
+ def execution_order(self) -> List[str]:
407
+ """Derives topological sort of build stages dynamically based on depends_on DAG."""
408
+ visited: Set[str] = set()
409
+ order: List[str] = []
410
+
411
+ def visit(node_id: str, path: Set[str]):
412
+ if node_id in path:
413
+ raise ValueError(
414
+ f"Cyclic dependency detected in build stage: {node_id}"
415
+ )
416
+ if node_id not in visited:
417
+ path.add(node_id)
418
+ stage = self.stages.get(node_id)
419
+ if stage:
420
+ for dep in stage.depends_on:
421
+ visit(dep, path)
422
+ path.remove(node_id)
423
+ visited.add(node_id)
424
+ order.append(node_id)
425
+
426
+ for s_id in self.stages:
427
+ visit(s_id, set())
428
+ return order
429
+
430
+ def validate_graph_invariants(self) -> List[str]:
431
+ """Validates structural integrity of the DAG, artifacts, and dependencies.
432
+
433
+ Invariants:
434
+ 1. Every depends_on stage exists in stages.
435
+ 2. No stage depends on itself.
436
+ 3. Every BuildArtifact producer_stage exists.
437
+ 4. Every BuildArtifact consumer_stage exists.
438
+ 5. For every stage dependency A depends_on B, A != B.
439
+ 6. Bi-directional fidelity: If artifact X is produced by P and consumed by C, C must declare depends_on P (or external source).
440
+ """
441
+ errors: List[str] = []
442
+
443
+ # 1. Validate Stage DAG
444
+ for s_id, stage in self.stages.items():
445
+ for dep in stage.depends_on:
446
+ if dep not in self.stages:
447
+ errors.append(
448
+ f"Stage '{s_id}' depends on non-existent stage '{dep}'."
449
+ )
450
+ if dep == s_id:
451
+ errors.append(f"Stage '{s_id}' cannot depend on itself.")
452
+
453
+ # 2. Validate Artifact Producer / Consumer Consistency
454
+ for a_id, art in self.artifacts.items():
455
+ if art.producer_stage not in self.stages:
456
+ errors.append(
457
+ f"Artifact '{a_id}' producer_stage '{art.producer_stage}' does not exist."
458
+ )
459
+ for c_stage in art.consumer_stages:
460
+ if c_stage not in self.stages:
461
+ errors.append(
462
+ f"Artifact '{a_id}' consumer_stage '{c_stage}' does not exist."
463
+ )
464
+ else:
465
+ # Bi-directional check: consuming stage must depend on producing stage
466
+ consumer_obj = self.stages[c_stage]
467
+ if (
468
+ art.producer_stage != c_stage
469
+ and art.producer_stage not in consumer_obj.depends_on
470
+ ):
471
+ errors.append(
472
+ f"Consuming stage '{c_stage}' consumes artifact '{a_id}' produced by '{art.producer_stage}', "
473
+ f"but '{c_stage}' does not declare depends_on '{art.producer_stage}'."
474
+ )
475
+
476
+ # 3. Validate Cycle-free DAG
477
+ try:
478
+ self.execution_order()
479
+ except ValueError as e:
480
+ errors.append(str(e))
481
+
482
+ return errors
483
+
484
+ def validate_locale_invariants(self) -> List[str]:
485
+ """Validates locale document lineage and fallback invariants.
486
+
487
+ Invariants:
488
+ 1. TRANSLATED: effective_source == localized_source and is_fallback is False.
489
+ 2. FALLBACK: effective_source == canonical_source, is_fallback is True, translation_notice_required is True.
490
+ 3. CANONICAL: effective_source == canonical_source, is_fallback is False, translation_notice_required is False.
491
+ """
492
+ errors: List[str] = []
493
+ for loc_id, spec in self.locales.items():
494
+ for route, doc in spec.documents.items():
495
+ if doc.translation_status == "TRANSLATED":
496
+ if doc.effective_source != doc.localized_source or doc.is_fallback:
497
+ errors.append(
498
+ f"Locale '{loc_id}' route '{route}' has TRANSLATED status but invalid effective_source or is_fallback."
499
+ )
500
+ elif doc.translation_status == "FALLBACK":
501
+ if (
502
+ doc.effective_source != doc.canonical_source
503
+ or not doc.is_fallback
504
+ or not doc.translation_notice_required
505
+ ):
506
+ errors.append(
507
+ f"Locale '{loc_id}' route '{route}' has FALLBACK status but invalid canonical mapping or missing notice."
508
+ )
509
+ elif doc.translation_status == "CANONICAL":
510
+ if (
511
+ doc.effective_source != doc.canonical_source
512
+ or doc.is_fallback
513
+ or doc.translation_notice_required
514
+ ):
515
+ errors.append(
516
+ f"Locale '{loc_id}' route '{route}' has CANONICAL status but invalid effective_source or notice flag."
517
+ )
518
+ return errors
519
+
520
+
521
+ class DocumentationSiteGraph(BaseModel):
522
+ schema_version: str = DOCUMENTATION_IR_SCHEMA_VERSION
523
+ pages: Dict[str, DocumentationPage] = Field(default_factory=dict)
524
+ navigation: Optional[NavigationNode] = None
525
+ generated_pipelines: List[GeneratedPipeline] = Field(default_factory=list)
526
+ build_graph: Optional[DocumentationBuildGraph] = None
527
+
528
+ def canonical_dict(self) -> Dict[str, Any]:
529
+ return self.model_dump()
530
+
531
+ def canonical_hash(self) -> str:
532
+ canonical_json = json.dumps(self.canonical_dict(), sort_keys=True, default=str)
533
+ return hashlib.sha256(canonical_json.encode("utf-8")).hexdigest()