julee 0.1.5__py3-none-any.whl → 0.1.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.
- julee/__init__.py +1 -1
- julee/contrib/polling/apps/worker/pipelines.py +3 -1
- julee/contrib/polling/tests/unit/apps/worker/test_pipelines.py +3 -0
- julee/docs/sphinx_hcd/__init__.py +146 -13
- julee/docs/sphinx_hcd/domain/__init__.py +5 -0
- julee/docs/sphinx_hcd/domain/models/__init__.py +32 -0
- julee/docs/sphinx_hcd/domain/models/accelerator.py +152 -0
- julee/docs/sphinx_hcd/domain/models/app.py +151 -0
- julee/docs/sphinx_hcd/domain/models/code_info.py +121 -0
- julee/docs/sphinx_hcd/domain/models/epic.py +79 -0
- julee/docs/sphinx_hcd/domain/models/integration.py +230 -0
- julee/docs/sphinx_hcd/domain/models/journey.py +222 -0
- julee/docs/sphinx_hcd/domain/models/persona.py +106 -0
- julee/docs/sphinx_hcd/domain/models/story.py +128 -0
- julee/docs/sphinx_hcd/domain/repositories/__init__.py +25 -0
- julee/docs/sphinx_hcd/domain/repositories/accelerator.py +98 -0
- julee/docs/sphinx_hcd/domain/repositories/app.py +57 -0
- julee/docs/sphinx_hcd/domain/repositories/base.py +89 -0
- julee/docs/sphinx_hcd/domain/repositories/code_info.py +69 -0
- julee/docs/sphinx_hcd/domain/repositories/epic.py +62 -0
- julee/docs/sphinx_hcd/domain/repositories/integration.py +79 -0
- julee/docs/sphinx_hcd/domain/repositories/journey.py +106 -0
- julee/docs/sphinx_hcd/domain/repositories/story.py +68 -0
- julee/docs/sphinx_hcd/domain/use_cases/__init__.py +64 -0
- julee/docs/sphinx_hcd/domain/use_cases/derive_personas.py +166 -0
- julee/docs/sphinx_hcd/domain/use_cases/resolve_accelerator_references.py +236 -0
- julee/docs/sphinx_hcd/domain/use_cases/resolve_app_references.py +144 -0
- julee/docs/sphinx_hcd/domain/use_cases/resolve_story_references.py +121 -0
- julee/docs/sphinx_hcd/parsers/__init__.py +48 -0
- julee/docs/sphinx_hcd/parsers/ast.py +150 -0
- julee/docs/sphinx_hcd/parsers/gherkin.py +155 -0
- julee/docs/sphinx_hcd/parsers/yaml.py +184 -0
- julee/docs/sphinx_hcd/repositories/__init__.py +4 -0
- julee/docs/sphinx_hcd/repositories/memory/__init__.py +25 -0
- julee/docs/sphinx_hcd/repositories/memory/accelerator.py +86 -0
- julee/docs/sphinx_hcd/repositories/memory/app.py +45 -0
- julee/docs/sphinx_hcd/repositories/memory/base.py +106 -0
- julee/docs/sphinx_hcd/repositories/memory/code_info.py +59 -0
- julee/docs/sphinx_hcd/repositories/memory/epic.py +54 -0
- julee/docs/sphinx_hcd/repositories/memory/integration.py +70 -0
- julee/docs/sphinx_hcd/repositories/memory/journey.py +96 -0
- julee/docs/sphinx_hcd/repositories/memory/story.py +63 -0
- julee/docs/sphinx_hcd/sphinx/__init__.py +28 -0
- julee/docs/sphinx_hcd/sphinx/adapters.py +116 -0
- julee/docs/sphinx_hcd/sphinx/context.py +163 -0
- julee/docs/sphinx_hcd/sphinx/directives/__init__.py +160 -0
- julee/docs/sphinx_hcd/sphinx/directives/accelerator.py +576 -0
- julee/docs/sphinx_hcd/sphinx/directives/app.py +349 -0
- julee/docs/sphinx_hcd/sphinx/directives/base.py +211 -0
- julee/docs/sphinx_hcd/sphinx/directives/epic.py +434 -0
- julee/docs/sphinx_hcd/sphinx/directives/integration.py +220 -0
- julee/docs/sphinx_hcd/sphinx/directives/journey.py +642 -0
- julee/docs/sphinx_hcd/sphinx/directives/persona.py +345 -0
- julee/docs/sphinx_hcd/sphinx/directives/story.py +575 -0
- julee/docs/sphinx_hcd/sphinx/event_handlers/__init__.py +16 -0
- julee/docs/sphinx_hcd/sphinx/event_handlers/builder_inited.py +31 -0
- julee/docs/sphinx_hcd/sphinx/event_handlers/doctree_read.py +27 -0
- julee/docs/sphinx_hcd/sphinx/event_handlers/doctree_resolved.py +43 -0
- julee/docs/sphinx_hcd/sphinx/event_handlers/env_purge_doc.py +42 -0
- julee/docs/sphinx_hcd/sphinx/initialization.py +139 -0
- julee/docs/sphinx_hcd/tests/__init__.py +9 -0
- julee/docs/sphinx_hcd/tests/conftest.py +6 -0
- julee/docs/sphinx_hcd/tests/domain/__init__.py +1 -0
- julee/docs/sphinx_hcd/tests/domain/models/__init__.py +1 -0
- julee/docs/sphinx_hcd/tests/domain/models/test_accelerator.py +266 -0
- julee/docs/sphinx_hcd/tests/domain/models/test_app.py +258 -0
- julee/docs/sphinx_hcd/tests/domain/models/test_code_info.py +231 -0
- julee/docs/sphinx_hcd/tests/domain/models/test_epic.py +163 -0
- julee/docs/sphinx_hcd/tests/domain/models/test_integration.py +327 -0
- julee/docs/sphinx_hcd/tests/domain/models/test_journey.py +249 -0
- julee/docs/sphinx_hcd/tests/domain/models/test_persona.py +172 -0
- julee/docs/sphinx_hcd/tests/domain/models/test_story.py +216 -0
- julee/docs/sphinx_hcd/tests/domain/use_cases/__init__.py +1 -0
- julee/docs/sphinx_hcd/tests/domain/use_cases/test_derive_personas.py +314 -0
- julee/docs/sphinx_hcd/tests/domain/use_cases/test_resolve_accelerator_references.py +476 -0
- julee/docs/sphinx_hcd/tests/domain/use_cases/test_resolve_app_references.py +265 -0
- julee/docs/sphinx_hcd/tests/domain/use_cases/test_resolve_story_references.py +229 -0
- julee/docs/sphinx_hcd/tests/integration/__init__.py +1 -0
- julee/docs/sphinx_hcd/tests/parsers/__init__.py +1 -0
- julee/docs/sphinx_hcd/tests/parsers/test_ast.py +298 -0
- julee/docs/sphinx_hcd/tests/parsers/test_gherkin.py +282 -0
- julee/docs/sphinx_hcd/tests/parsers/test_yaml.py +496 -0
- julee/docs/sphinx_hcd/tests/repositories/__init__.py +1 -0
- julee/docs/sphinx_hcd/tests/repositories/test_accelerator.py +298 -0
- julee/docs/sphinx_hcd/tests/repositories/test_app.py +218 -0
- julee/docs/sphinx_hcd/tests/repositories/test_base.py +151 -0
- julee/docs/sphinx_hcd/tests/repositories/test_code_info.py +253 -0
- julee/docs/sphinx_hcd/tests/repositories/test_epic.py +237 -0
- julee/docs/sphinx_hcd/tests/repositories/test_integration.py +268 -0
- julee/docs/sphinx_hcd/tests/repositories/test_journey.py +294 -0
- julee/docs/sphinx_hcd/tests/repositories/test_story.py +236 -0
- julee/docs/sphinx_hcd/tests/sphinx/__init__.py +1 -0
- julee/docs/sphinx_hcd/tests/sphinx/directives/__init__.py +1 -0
- julee/docs/sphinx_hcd/tests/sphinx/directives/test_base.py +160 -0
- julee/docs/sphinx_hcd/tests/sphinx/test_adapters.py +176 -0
- julee/docs/sphinx_hcd/tests/sphinx/test_context.py +257 -0
- {julee-0.1.5.dist-info → julee-0.1.7.dist-info}/METADATA +2 -1
- {julee-0.1.5.dist-info → julee-0.1.7.dist-info}/RECORD +101 -16
- julee/docs/sphinx_hcd/accelerators.py +0 -1175
- julee/docs/sphinx_hcd/apps.py +0 -518
- julee/docs/sphinx_hcd/epics.py +0 -453
- julee/docs/sphinx_hcd/integrations.py +0 -310
- julee/docs/sphinx_hcd/journeys.py +0 -797
- julee/docs/sphinx_hcd/personas.py +0 -457
- julee/docs/sphinx_hcd/stories.py +0 -960
- {julee-0.1.5.dist-info → julee-0.1.7.dist-info}/WHEEL +0 -0
- {julee-0.1.5.dist-info → julee-0.1.7.dist-info}/licenses/LICENSE +0 -0
- {julee-0.1.5.dist-info → julee-0.1.7.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Memory implementation of JourneyRepository."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from ...domain.models.journey import Journey
|
|
6
|
+
from ...domain.repositories.journey import JourneyRepository
|
|
7
|
+
from ...utils import normalize_name
|
|
8
|
+
from .base import MemoryRepositoryMixin
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MemoryJourneyRepository(MemoryRepositoryMixin[Journey], JourneyRepository):
|
|
14
|
+
"""In-memory implementation of JourneyRepository.
|
|
15
|
+
|
|
16
|
+
Journeys are stored in a dictionary keyed by slug. This implementation
|
|
17
|
+
is used during Sphinx builds where journeys are populated during doctree
|
|
18
|
+
processing and support incremental builds via docname tracking.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self) -> None:
|
|
22
|
+
"""Initialize with empty storage."""
|
|
23
|
+
self.storage: dict[str, Journey] = {}
|
|
24
|
+
self.entity_name = "Journey"
|
|
25
|
+
self.id_field = "slug"
|
|
26
|
+
|
|
27
|
+
async def get_by_persona(self, persona: str) -> list[Journey]:
|
|
28
|
+
"""Get all journeys for a persona."""
|
|
29
|
+
persona_normalized = normalize_name(persona)
|
|
30
|
+
return [
|
|
31
|
+
journey
|
|
32
|
+
for journey in self.storage.values()
|
|
33
|
+
if journey.persona_normalized == persona_normalized
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
async def get_by_docname(self, docname: str) -> list[Journey]:
|
|
37
|
+
"""Get all journeys defined in a specific document."""
|
|
38
|
+
return [
|
|
39
|
+
journey for journey in self.storage.values() if journey.docname == docname
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
async def clear_by_docname(self, docname: str) -> int:
|
|
43
|
+
"""Remove all journeys defined in a specific document."""
|
|
44
|
+
to_remove = [
|
|
45
|
+
slug for slug, journey in self.storage.items() if journey.docname == docname
|
|
46
|
+
]
|
|
47
|
+
for slug in to_remove:
|
|
48
|
+
del self.storage[slug]
|
|
49
|
+
return len(to_remove)
|
|
50
|
+
|
|
51
|
+
async def get_dependents(self, journey_slug: str) -> list[Journey]:
|
|
52
|
+
"""Get journeys that depend on a specific journey."""
|
|
53
|
+
return [
|
|
54
|
+
journey
|
|
55
|
+
for journey in self.storage.values()
|
|
56
|
+
if journey.has_dependency(journey_slug)
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
async def get_dependencies(self, journey_slug: str) -> list[Journey]:
|
|
60
|
+
"""Get journeys that a specific journey depends on."""
|
|
61
|
+
journey = self.storage.get(journey_slug)
|
|
62
|
+
if not journey:
|
|
63
|
+
return []
|
|
64
|
+
return [
|
|
65
|
+
self.storage[dep_slug]
|
|
66
|
+
for dep_slug in journey.depends_on
|
|
67
|
+
if dep_slug in self.storage
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
async def get_all_personas(self) -> set[str]:
|
|
71
|
+
"""Get all unique personas across all journeys."""
|
|
72
|
+
return {
|
|
73
|
+
journey.persona_normalized
|
|
74
|
+
for journey in self.storage.values()
|
|
75
|
+
if journey.persona_normalized
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async def get_with_story_ref(self, story_title: str) -> list[Journey]:
|
|
79
|
+
"""Get journeys that reference a specific story."""
|
|
80
|
+
story_normalized = normalize_name(story_title)
|
|
81
|
+
return [
|
|
82
|
+
journey
|
|
83
|
+
for journey in self.storage.values()
|
|
84
|
+
if any(
|
|
85
|
+
normalize_name(ref) == story_normalized
|
|
86
|
+
for ref in journey.get_story_refs()
|
|
87
|
+
)
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
async def get_with_epic_ref(self, epic_slug: str) -> list[Journey]:
|
|
91
|
+
"""Get journeys that reference a specific epic."""
|
|
92
|
+
return [
|
|
93
|
+
journey
|
|
94
|
+
for journey in self.storage.values()
|
|
95
|
+
if epic_slug in journey.get_epic_refs()
|
|
96
|
+
]
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Memory implementation of StoryRepository."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from ...domain.models.story import Story
|
|
6
|
+
from ...domain.repositories.story import StoryRepository
|
|
7
|
+
from ...utils import normalize_name
|
|
8
|
+
from .base import MemoryRepositoryMixin
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MemoryStoryRepository(MemoryRepositoryMixin[Story], StoryRepository):
|
|
14
|
+
"""In-memory implementation of StoryRepository.
|
|
15
|
+
|
|
16
|
+
Stories are stored in a dictionary keyed by slug. This implementation
|
|
17
|
+
is used during Sphinx builds where stories are populated at builder-inited
|
|
18
|
+
and queried during doctree processing.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self) -> None:
|
|
22
|
+
"""Initialize with empty storage."""
|
|
23
|
+
self.storage: dict[str, Story] = {}
|
|
24
|
+
self.entity_name = "Story"
|
|
25
|
+
self.id_field = "slug"
|
|
26
|
+
|
|
27
|
+
async def get_by_app(self, app_slug: str) -> list[Story]:
|
|
28
|
+
"""Get all stories for an application."""
|
|
29
|
+
app_normalized = normalize_name(app_slug)
|
|
30
|
+
return [
|
|
31
|
+
story
|
|
32
|
+
for story in self.storage.values()
|
|
33
|
+
if story.app_normalized == app_normalized
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
async def get_by_persona(self, persona: str) -> list[Story]:
|
|
37
|
+
"""Get all stories for a persona."""
|
|
38
|
+
persona_normalized = normalize_name(persona)
|
|
39
|
+
return [
|
|
40
|
+
story
|
|
41
|
+
for story in self.storage.values()
|
|
42
|
+
if story.persona_normalized == persona_normalized
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
async def get_by_feature_title(self, feature_title: str) -> Story | None:
|
|
46
|
+
"""Get a story by its feature title."""
|
|
47
|
+
title_normalized = normalize_name(feature_title)
|
|
48
|
+
for story in self.storage.values():
|
|
49
|
+
if normalize_name(story.feature_title) == title_normalized:
|
|
50
|
+
return story
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
async def get_apps_with_stories(self) -> set[str]:
|
|
54
|
+
"""Get the set of app slugs that have stories."""
|
|
55
|
+
return {story.app_slug for story in self.storage.values()}
|
|
56
|
+
|
|
57
|
+
async def get_all_personas(self) -> set[str]:
|
|
58
|
+
"""Get all unique personas across all stories."""
|
|
59
|
+
return {
|
|
60
|
+
story.persona_normalized
|
|
61
|
+
for story in self.storage.values()
|
|
62
|
+
if story.persona_normalized != "unknown"
|
|
63
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Sphinx application layer for sphinx_hcd.
|
|
2
|
+
|
|
3
|
+
Contains Sphinx-specific code:
|
|
4
|
+
- adapters.py: SyncRepositoryAdapter for sync access to async repos
|
|
5
|
+
- context.py: HCDContext for unified repository access
|
|
6
|
+
- initialization.py: Builder-inited handlers
|
|
7
|
+
- directives/: Sphinx directive implementations
|
|
8
|
+
- event_handlers/: Sphinx lifecycle event handlers
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .adapters import SyncRepositoryAdapter
|
|
12
|
+
from .context import (
|
|
13
|
+
HCDContext,
|
|
14
|
+
ensure_hcd_context,
|
|
15
|
+
get_hcd_context,
|
|
16
|
+
set_hcd_context,
|
|
17
|
+
)
|
|
18
|
+
from .initialization import initialize_hcd_context, purge_doc_from_context
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"HCDContext",
|
|
22
|
+
"SyncRepositoryAdapter",
|
|
23
|
+
"ensure_hcd_context",
|
|
24
|
+
"get_hcd_context",
|
|
25
|
+
"initialize_hcd_context",
|
|
26
|
+
"purge_doc_from_context",
|
|
27
|
+
"set_hcd_context",
|
|
28
|
+
]
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Sync adapters for async repositories.
|
|
2
|
+
|
|
3
|
+
Sphinx directives are synchronous, but our domain repositories are async
|
|
4
|
+
(following julee patterns). This module provides adapters to bridge the gap.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
from typing import Any, Generic, TypeVar
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel
|
|
11
|
+
|
|
12
|
+
from ..domain.repositories.base import BaseRepository
|
|
13
|
+
|
|
14
|
+
T = TypeVar("T", bound=BaseModel)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SyncRepositoryAdapter(Generic[T]):
|
|
18
|
+
"""Synchronous wrapper for async repository methods.
|
|
19
|
+
|
|
20
|
+
Provides a synchronous interface to async repositories for use in
|
|
21
|
+
Sphinx directives. Uses asyncio.run() to execute async methods.
|
|
22
|
+
|
|
23
|
+
Example:
|
|
24
|
+
>>> async_repo = MemoryStoryRepository()
|
|
25
|
+
>>> sync_repo = SyncRepositoryAdapter(async_repo)
|
|
26
|
+
>>> story = sync_repo.get("my-story-slug") # Sync call
|
|
27
|
+
|
|
28
|
+
Note:
|
|
29
|
+
This adapter is designed for use in Sphinx's synchronous directive
|
|
30
|
+
system. The overhead of asyncio.run() is negligible for in-memory
|
|
31
|
+
repositories.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, async_repo: BaseRepository[T]) -> None:
|
|
35
|
+
"""Initialize with an async repository.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
async_repo: An async repository implementing BaseRepository[T]
|
|
39
|
+
"""
|
|
40
|
+
self._repo = async_repo
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def async_repo(self) -> BaseRepository[T]:
|
|
44
|
+
"""Access the underlying async repository."""
|
|
45
|
+
return self._repo
|
|
46
|
+
|
|
47
|
+
def get(self, entity_id: str) -> T | None:
|
|
48
|
+
"""Retrieve an entity by ID (sync wrapper).
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
entity_id: Unique entity identifier
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Entity if found, None otherwise
|
|
55
|
+
"""
|
|
56
|
+
return asyncio.run(self._repo.get(entity_id))
|
|
57
|
+
|
|
58
|
+
def get_many(self, entity_ids: list[str]) -> dict[str, T | None]:
|
|
59
|
+
"""Retrieve multiple entities by ID (sync wrapper).
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
entity_ids: List of unique entity identifiers
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Dict mapping entity_id to entity (or None if not found)
|
|
66
|
+
"""
|
|
67
|
+
return asyncio.run(self._repo.get_many(entity_ids))
|
|
68
|
+
|
|
69
|
+
def save(self, entity: T) -> None:
|
|
70
|
+
"""Save an entity (sync wrapper).
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
entity: Complete entity to save
|
|
74
|
+
"""
|
|
75
|
+
asyncio.run(self._repo.save(entity))
|
|
76
|
+
|
|
77
|
+
def list_all(self) -> list[T]:
|
|
78
|
+
"""List all entities (sync wrapper).
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
List of all entities in the repository
|
|
82
|
+
"""
|
|
83
|
+
return asyncio.run(self._repo.list_all())
|
|
84
|
+
|
|
85
|
+
def delete(self, entity_id: str) -> bool:
|
|
86
|
+
"""Delete an entity by ID (sync wrapper).
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
entity_id: Unique entity identifier
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
True if entity was deleted, False if not found
|
|
93
|
+
"""
|
|
94
|
+
return asyncio.run(self._repo.delete(entity_id))
|
|
95
|
+
|
|
96
|
+
def clear(self) -> None:
|
|
97
|
+
"""Remove all entities from the repository (sync wrapper)."""
|
|
98
|
+
asyncio.run(self._repo.clear())
|
|
99
|
+
|
|
100
|
+
def run_async(self, coro: Any) -> Any:
|
|
101
|
+
"""Run an arbitrary async method on the underlying repository.
|
|
102
|
+
|
|
103
|
+
Useful for repository-specific methods not in BaseRepository.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
coro: A coroutine to execute
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
The result of the coroutine
|
|
110
|
+
|
|
111
|
+
Example:
|
|
112
|
+
>>> result = sync_repo.run_async(
|
|
113
|
+
... sync_repo.async_repo.find_by_persona("Staff Member")
|
|
114
|
+
... )
|
|
115
|
+
"""
|
|
116
|
+
return asyncio.run(coro)
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""HCDContext for unified repository access.
|
|
2
|
+
|
|
3
|
+
Provides a single context object that holds all repositories for the
|
|
4
|
+
HCD documentation system. This replaces the scattered global/env registries
|
|
5
|
+
with a unified, type-safe interface.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
from ..repositories.memory import (
|
|
12
|
+
MemoryAcceleratorRepository,
|
|
13
|
+
MemoryAppRepository,
|
|
14
|
+
MemoryCodeInfoRepository,
|
|
15
|
+
MemoryEpicRepository,
|
|
16
|
+
MemoryIntegrationRepository,
|
|
17
|
+
MemoryJourneyRepository,
|
|
18
|
+
MemoryStoryRepository,
|
|
19
|
+
)
|
|
20
|
+
from .adapters import SyncRepositoryAdapter
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from ..domain.models import (
|
|
24
|
+
Accelerator,
|
|
25
|
+
App,
|
|
26
|
+
BoundedContextInfo,
|
|
27
|
+
Epic,
|
|
28
|
+
Integration,
|
|
29
|
+
Journey,
|
|
30
|
+
Story,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class HCDContext:
|
|
36
|
+
"""Unified context for HCD documentation.
|
|
37
|
+
|
|
38
|
+
Holds all repositories needed for the HCD documentation system.
|
|
39
|
+
Each repository is wrapped in a SyncRepositoryAdapter for use in
|
|
40
|
+
Sphinx's synchronous directive system.
|
|
41
|
+
|
|
42
|
+
This context is created at builder-inited and attached to the
|
|
43
|
+
Sphinx app object. It can be retrieved using get_hcd_context().
|
|
44
|
+
|
|
45
|
+
Attributes:
|
|
46
|
+
story_repo: Repository for Story entities
|
|
47
|
+
journey_repo: Repository for Journey entities
|
|
48
|
+
epic_repo: Repository for Epic entities
|
|
49
|
+
app_repo: Repository for App entities
|
|
50
|
+
accelerator_repo: Repository for Accelerator entities
|
|
51
|
+
integration_repo: Repository for Integration entities
|
|
52
|
+
code_info_repo: Repository for BoundedContextInfo entities
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
story_repo: SyncRepositoryAdapter["Story"] = field(
|
|
56
|
+
default_factory=lambda: SyncRepositoryAdapter(MemoryStoryRepository())
|
|
57
|
+
)
|
|
58
|
+
journey_repo: SyncRepositoryAdapter["Journey"] = field(
|
|
59
|
+
default_factory=lambda: SyncRepositoryAdapter(MemoryJourneyRepository())
|
|
60
|
+
)
|
|
61
|
+
epic_repo: SyncRepositoryAdapter["Epic"] = field(
|
|
62
|
+
default_factory=lambda: SyncRepositoryAdapter(MemoryEpicRepository())
|
|
63
|
+
)
|
|
64
|
+
app_repo: SyncRepositoryAdapter["App"] = field(
|
|
65
|
+
default_factory=lambda: SyncRepositoryAdapter(MemoryAppRepository())
|
|
66
|
+
)
|
|
67
|
+
accelerator_repo: SyncRepositoryAdapter["Accelerator"] = field(
|
|
68
|
+
default_factory=lambda: SyncRepositoryAdapter(MemoryAcceleratorRepository())
|
|
69
|
+
)
|
|
70
|
+
integration_repo: SyncRepositoryAdapter["Integration"] = field(
|
|
71
|
+
default_factory=lambda: SyncRepositoryAdapter(MemoryIntegrationRepository())
|
|
72
|
+
)
|
|
73
|
+
code_info_repo: SyncRepositoryAdapter["BoundedContextInfo"] = field(
|
|
74
|
+
default_factory=lambda: SyncRepositoryAdapter(MemoryCodeInfoRepository())
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def clear_all(self) -> None:
|
|
78
|
+
"""Clear all repositories.
|
|
79
|
+
|
|
80
|
+
Useful for testing or when rebuilding documentation from scratch.
|
|
81
|
+
"""
|
|
82
|
+
self.story_repo.clear()
|
|
83
|
+
self.journey_repo.clear()
|
|
84
|
+
self.epic_repo.clear()
|
|
85
|
+
self.app_repo.clear()
|
|
86
|
+
self.accelerator_repo.clear()
|
|
87
|
+
self.integration_repo.clear()
|
|
88
|
+
self.code_info_repo.clear()
|
|
89
|
+
|
|
90
|
+
def clear_by_docname(self, docname: str) -> dict[str, int]:
|
|
91
|
+
"""Clear entities defined in a specific document.
|
|
92
|
+
|
|
93
|
+
Used during incremental builds when a document is re-read.
|
|
94
|
+
Only entities that track docname are cleared (journey, epic, accelerator).
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
docname: RST document name
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Dict mapping entity type to number of entities removed
|
|
101
|
+
"""
|
|
102
|
+
results = {}
|
|
103
|
+
|
|
104
|
+
# Journey repo has clear_by_docname
|
|
105
|
+
journey_async = self.journey_repo.async_repo
|
|
106
|
+
results["journeys"] = self.journey_repo.run_async(
|
|
107
|
+
journey_async.clear_by_docname(docname) # type: ignore
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Epic repo has clear_by_docname
|
|
111
|
+
epic_async = self.epic_repo.async_repo
|
|
112
|
+
results["epics"] = self.epic_repo.run_async(
|
|
113
|
+
epic_async.clear_by_docname(docname) # type: ignore
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Accelerator repo has clear_by_docname
|
|
117
|
+
accel_async = self.accelerator_repo.async_repo
|
|
118
|
+
results["accelerators"] = self.accelerator_repo.run_async(
|
|
119
|
+
accel_async.clear_by_docname(docname) # type: ignore
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
return results
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def get_hcd_context(app) -> HCDContext:
|
|
126
|
+
"""Get the HCDContext from a Sphinx app.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
app: Sphinx application object
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
HCDContext attached to the app
|
|
133
|
+
|
|
134
|
+
Raises:
|
|
135
|
+
AttributeError: If context hasn't been initialized
|
|
136
|
+
"""
|
|
137
|
+
return app._hcd_context
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def set_hcd_context(app, context: HCDContext) -> None:
|
|
141
|
+
"""Set the HCDContext on a Sphinx app.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
app: Sphinx application object
|
|
145
|
+
context: HCDContext to attach
|
|
146
|
+
"""
|
|
147
|
+
app._hcd_context = context
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def ensure_hcd_context(app) -> HCDContext:
|
|
151
|
+
"""Ensure the HCDContext exists on a Sphinx app.
|
|
152
|
+
|
|
153
|
+
Creates a new context if one doesn't exist.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
app: Sphinx application object
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
HCDContext attached to the app
|
|
160
|
+
"""
|
|
161
|
+
if not hasattr(app, "_hcd_context"):
|
|
162
|
+
set_hcd_context(app, HCDContext())
|
|
163
|
+
return get_hcd_context(app)
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""Sphinx directives for sphinx_hcd.
|
|
2
|
+
|
|
3
|
+
Thin directive adapters that use domain models and repositories.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .accelerator import (
|
|
7
|
+
AcceleratorDependencyDiagramDirective,
|
|
8
|
+
AcceleratorDependencyDiagramPlaceholder,
|
|
9
|
+
AcceleratorIndexDirective,
|
|
10
|
+
AcceleratorIndexPlaceholder,
|
|
11
|
+
AcceleratorsForAppDirective,
|
|
12
|
+
AcceleratorsForAppPlaceholder,
|
|
13
|
+
AcceleratorStatusDirective,
|
|
14
|
+
DefineAcceleratorDirective,
|
|
15
|
+
DefineAcceleratorPlaceholder,
|
|
16
|
+
DependentAcceleratorsDirective,
|
|
17
|
+
DependentAcceleratorsPlaceholder,
|
|
18
|
+
clear_accelerator_state,
|
|
19
|
+
process_accelerator_placeholders,
|
|
20
|
+
)
|
|
21
|
+
from .app import (
|
|
22
|
+
AppIndexDirective,
|
|
23
|
+
AppIndexPlaceholder,
|
|
24
|
+
AppsForPersonaDirective,
|
|
25
|
+
AppsForPersonaPlaceholder,
|
|
26
|
+
DefineAppDirective,
|
|
27
|
+
DefineAppPlaceholder,
|
|
28
|
+
process_app_placeholders,
|
|
29
|
+
)
|
|
30
|
+
from .base import HCDDirective, make_deprecated_directive
|
|
31
|
+
from .epic import (
|
|
32
|
+
DefineEpicDirective,
|
|
33
|
+
EpicIndexDirective,
|
|
34
|
+
EpicIndexPlaceholder,
|
|
35
|
+
EpicsForPersonaDirective,
|
|
36
|
+
EpicsForPersonaPlaceholder,
|
|
37
|
+
EpicStoryDirective,
|
|
38
|
+
clear_epic_state,
|
|
39
|
+
process_epic_placeholders,
|
|
40
|
+
)
|
|
41
|
+
from .integration import (
|
|
42
|
+
DefineIntegrationDirective,
|
|
43
|
+
DefineIntegrationPlaceholder,
|
|
44
|
+
IntegrationIndexDirective,
|
|
45
|
+
IntegrationIndexPlaceholder,
|
|
46
|
+
process_integration_placeholders,
|
|
47
|
+
)
|
|
48
|
+
from .journey import (
|
|
49
|
+
DefineJourneyDirective,
|
|
50
|
+
JourneyDependencyGraphDirective,
|
|
51
|
+
JourneyDependencyGraphPlaceholder,
|
|
52
|
+
JourneyIndexDirective,
|
|
53
|
+
JourneysForPersonaDirective,
|
|
54
|
+
StepEpicDirective,
|
|
55
|
+
StepPhaseDirective,
|
|
56
|
+
StepStoryDirective,
|
|
57
|
+
clear_journey_state,
|
|
58
|
+
process_dependency_graph_placeholder,
|
|
59
|
+
process_journey_steps,
|
|
60
|
+
)
|
|
61
|
+
from .persona import (
|
|
62
|
+
PersonaDiagramDirective,
|
|
63
|
+
PersonaDiagramPlaceholder,
|
|
64
|
+
PersonaIndexDiagramDirective,
|
|
65
|
+
PersonaIndexDiagramPlaceholder,
|
|
66
|
+
process_persona_placeholders,
|
|
67
|
+
)
|
|
68
|
+
from .story import (
|
|
69
|
+
GherkinAppStoriesDirective,
|
|
70
|
+
GherkinStoriesDirective,
|
|
71
|
+
GherkinStoriesForAppDirective,
|
|
72
|
+
GherkinStoriesForPersonaDirective,
|
|
73
|
+
GherkinStoriesIndexDirective,
|
|
74
|
+
GherkinStoryDirective,
|
|
75
|
+
StoriesDirective,
|
|
76
|
+
StoryAppDirective,
|
|
77
|
+
StoryIndexDirective,
|
|
78
|
+
StoryListForAppDirective,
|
|
79
|
+
StoryListForPersonaDirective,
|
|
80
|
+
StoryRefDirective,
|
|
81
|
+
StorySeeAlsoPlaceholder,
|
|
82
|
+
process_story_seealso_placeholders,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
__all__ = [
|
|
86
|
+
# Base
|
|
87
|
+
"HCDDirective",
|
|
88
|
+
"make_deprecated_directive",
|
|
89
|
+
# Story directives
|
|
90
|
+
"StoryAppDirective",
|
|
91
|
+
"StoryListForPersonaDirective",
|
|
92
|
+
"StoryListForAppDirective",
|
|
93
|
+
"StoryIndexDirective",
|
|
94
|
+
"StoriesDirective",
|
|
95
|
+
"StoryRefDirective",
|
|
96
|
+
"StorySeeAlsoPlaceholder",
|
|
97
|
+
"process_story_seealso_placeholders",
|
|
98
|
+
# Story deprecated aliases
|
|
99
|
+
"GherkinStoryDirective",
|
|
100
|
+
"GherkinStoriesDirective",
|
|
101
|
+
"GherkinStoriesForPersonaDirective",
|
|
102
|
+
"GherkinStoriesForAppDirective",
|
|
103
|
+
"GherkinStoriesIndexDirective",
|
|
104
|
+
"GherkinAppStoriesDirective",
|
|
105
|
+
# Journey directives
|
|
106
|
+
"DefineJourneyDirective",
|
|
107
|
+
"StepStoryDirective",
|
|
108
|
+
"StepEpicDirective",
|
|
109
|
+
"StepPhaseDirective",
|
|
110
|
+
"JourneyIndexDirective",
|
|
111
|
+
"JourneyDependencyGraphDirective",
|
|
112
|
+
"JourneyDependencyGraphPlaceholder",
|
|
113
|
+
"JourneysForPersonaDirective",
|
|
114
|
+
"clear_journey_state",
|
|
115
|
+
"process_journey_steps",
|
|
116
|
+
"process_dependency_graph_placeholder",
|
|
117
|
+
# Epic directives
|
|
118
|
+
"DefineEpicDirective",
|
|
119
|
+
"EpicStoryDirective",
|
|
120
|
+
"EpicIndexDirective",
|
|
121
|
+
"EpicIndexPlaceholder",
|
|
122
|
+
"EpicsForPersonaDirective",
|
|
123
|
+
"EpicsForPersonaPlaceholder",
|
|
124
|
+
"clear_epic_state",
|
|
125
|
+
"process_epic_placeholders",
|
|
126
|
+
# App directives
|
|
127
|
+
"DefineAppDirective",
|
|
128
|
+
"DefineAppPlaceholder",
|
|
129
|
+
"AppIndexDirective",
|
|
130
|
+
"AppIndexPlaceholder",
|
|
131
|
+
"AppsForPersonaDirective",
|
|
132
|
+
"AppsForPersonaPlaceholder",
|
|
133
|
+
"process_app_placeholders",
|
|
134
|
+
# Accelerator directives
|
|
135
|
+
"DefineAcceleratorDirective",
|
|
136
|
+
"DefineAcceleratorPlaceholder",
|
|
137
|
+
"AcceleratorIndexDirective",
|
|
138
|
+
"AcceleratorIndexPlaceholder",
|
|
139
|
+
"AcceleratorsForAppDirective",
|
|
140
|
+
"AcceleratorsForAppPlaceholder",
|
|
141
|
+
"DependentAcceleratorsDirective",
|
|
142
|
+
"DependentAcceleratorsPlaceholder",
|
|
143
|
+
"AcceleratorDependencyDiagramDirective",
|
|
144
|
+
"AcceleratorDependencyDiagramPlaceholder",
|
|
145
|
+
"AcceleratorStatusDirective",
|
|
146
|
+
"clear_accelerator_state",
|
|
147
|
+
"process_accelerator_placeholders",
|
|
148
|
+
# Integration directives
|
|
149
|
+
"DefineIntegrationDirective",
|
|
150
|
+
"DefineIntegrationPlaceholder",
|
|
151
|
+
"IntegrationIndexDirective",
|
|
152
|
+
"IntegrationIndexPlaceholder",
|
|
153
|
+
"process_integration_placeholders",
|
|
154
|
+
# Persona directives
|
|
155
|
+
"PersonaDiagramDirective",
|
|
156
|
+
"PersonaDiagramPlaceholder",
|
|
157
|
+
"PersonaIndexDiagramDirective",
|
|
158
|
+
"PersonaIndexDiagramPlaceholder",
|
|
159
|
+
"process_persona_placeholders",
|
|
160
|
+
]
|