julee 0.1.5__py3-none-any.whl → 0.1.6__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/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.6.dist-info}/METADATA +2 -1
- {julee-0.1.5.dist-info → julee-0.1.6.dist-info}/RECORD +98 -13
- 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.6.dist-info}/WHEEL +0 -0
- {julee-0.1.5.dist-info → julee-0.1.6.dist-info}/licenses/LICENSE +0 -0
- {julee-0.1.5.dist-info → julee-0.1.6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Story domain model.
|
|
2
|
+
|
|
3
|
+
Represents a user story extracted from a Gherkin .feature file.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, field_validator
|
|
7
|
+
|
|
8
|
+
from ...utils import normalize_name, slugify
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Story(BaseModel):
|
|
12
|
+
"""A user story extracted from a Gherkin feature file.
|
|
13
|
+
|
|
14
|
+
Stories are the primary unit of user-facing functionality in HCD.
|
|
15
|
+
They capture who wants to do what and why.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
slug: URL-safe identifier derived from feature title
|
|
19
|
+
feature_title: The Feature: line from the Gherkin file
|
|
20
|
+
persona: The actor from "As a <persona>"
|
|
21
|
+
persona_normalized: Lowercase, spaces-normalized persona for matching
|
|
22
|
+
i_want: The action from "I want to <action>"
|
|
23
|
+
so_that: The benefit from "So that <benefit>"
|
|
24
|
+
app_slug: The application this story belongs to
|
|
25
|
+
app_normalized: Lowercase, spaces-normalized app name for matching
|
|
26
|
+
file_path: Relative path to the .feature file
|
|
27
|
+
abs_path: Absolute path to the .feature file
|
|
28
|
+
gherkin_snippet: The story header portion of the feature file
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
slug: str
|
|
32
|
+
feature_title: str
|
|
33
|
+
persona: str
|
|
34
|
+
persona_normalized: str = ""
|
|
35
|
+
i_want: str = "do something"
|
|
36
|
+
so_that: str = "achieve a goal"
|
|
37
|
+
app_slug: str
|
|
38
|
+
app_normalized: str = ""
|
|
39
|
+
file_path: str
|
|
40
|
+
abs_path: str = ""
|
|
41
|
+
gherkin_snippet: str = ""
|
|
42
|
+
|
|
43
|
+
@field_validator("slug")
|
|
44
|
+
@classmethod
|
|
45
|
+
def validate_slug(cls, v: str) -> str:
|
|
46
|
+
"""Ensure slug is not empty."""
|
|
47
|
+
if not v or not v.strip():
|
|
48
|
+
raise ValueError("Story slug cannot be empty")
|
|
49
|
+
return v.strip()
|
|
50
|
+
|
|
51
|
+
@field_validator("feature_title")
|
|
52
|
+
@classmethod
|
|
53
|
+
def validate_feature_title(cls, v: str) -> str:
|
|
54
|
+
"""Ensure feature title is not empty."""
|
|
55
|
+
if not v or not v.strip():
|
|
56
|
+
raise ValueError("Feature title cannot be empty")
|
|
57
|
+
return v.strip()
|
|
58
|
+
|
|
59
|
+
@field_validator("persona")
|
|
60
|
+
@classmethod
|
|
61
|
+
def validate_persona(cls, v: str) -> str:
|
|
62
|
+
"""Ensure persona is not empty, default to 'unknown'."""
|
|
63
|
+
if not v or not v.strip():
|
|
64
|
+
return "unknown"
|
|
65
|
+
return v.strip()
|
|
66
|
+
|
|
67
|
+
@field_validator("app_slug")
|
|
68
|
+
@classmethod
|
|
69
|
+
def validate_app_slug(cls, v: str) -> str:
|
|
70
|
+
"""Ensure app slug is not empty, default to 'unknown'."""
|
|
71
|
+
if not v or not v.strip():
|
|
72
|
+
return "unknown"
|
|
73
|
+
return v.strip()
|
|
74
|
+
|
|
75
|
+
def model_post_init(self, __context) -> None:
|
|
76
|
+
"""Compute normalized fields after initialization."""
|
|
77
|
+
if not self.persona_normalized:
|
|
78
|
+
self.persona_normalized = normalize_name(self.persona)
|
|
79
|
+
if not self.app_normalized:
|
|
80
|
+
self.app_normalized = normalize_name(self.app_slug)
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def from_feature_file(
|
|
84
|
+
cls,
|
|
85
|
+
feature_title: str,
|
|
86
|
+
persona: str,
|
|
87
|
+
i_want: str,
|
|
88
|
+
so_that: str,
|
|
89
|
+
app_slug: str,
|
|
90
|
+
file_path: str,
|
|
91
|
+
abs_path: str = "",
|
|
92
|
+
gherkin_snippet: str = "",
|
|
93
|
+
) -> "Story":
|
|
94
|
+
"""Create a Story from parsed feature file data.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
feature_title: The Feature: line content
|
|
98
|
+
persona: The "As a" actor
|
|
99
|
+
i_want: The "I want to" action
|
|
100
|
+
so_that: The "So that" benefit
|
|
101
|
+
app_slug: Application slug (from directory structure)
|
|
102
|
+
file_path: Relative path to .feature file
|
|
103
|
+
abs_path: Absolute path to .feature file
|
|
104
|
+
gherkin_snippet: The story header text
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
A new Story instance
|
|
108
|
+
"""
|
|
109
|
+
# Include app_slug in slug to avoid collisions between apps
|
|
110
|
+
return cls(
|
|
111
|
+
slug=f"{app_slug}--{slugify(feature_title)}",
|
|
112
|
+
feature_title=feature_title,
|
|
113
|
+
persona=persona,
|
|
114
|
+
i_want=i_want,
|
|
115
|
+
so_that=so_that,
|
|
116
|
+
app_slug=app_slug,
|
|
117
|
+
file_path=file_path,
|
|
118
|
+
abs_path=abs_path,
|
|
119
|
+
gherkin_snippet=gherkin_snippet,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
def matches_persona(self, persona_name: str) -> bool:
|
|
123
|
+
"""Check if this story belongs to a persona (case-insensitive)."""
|
|
124
|
+
return self.persona_normalized == normalize_name(persona_name)
|
|
125
|
+
|
|
126
|
+
def matches_app(self, app_name: str) -> bool:
|
|
127
|
+
"""Check if this story belongs to an app (case-insensitive)."""
|
|
128
|
+
return self.app_normalized == normalize_name(app_name)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Repository protocols for sphinx_hcd.
|
|
2
|
+
|
|
3
|
+
Defines async repository interfaces following julee patterns.
|
|
4
|
+
Implementations live in the repositories/ directory.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .accelerator import AcceleratorRepository
|
|
8
|
+
from .app import AppRepository
|
|
9
|
+
from .base import BaseRepository
|
|
10
|
+
from .code_info import CodeInfoRepository
|
|
11
|
+
from .epic import EpicRepository
|
|
12
|
+
from .integration import IntegrationRepository
|
|
13
|
+
from .journey import JourneyRepository
|
|
14
|
+
from .story import StoryRepository
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"AcceleratorRepository",
|
|
18
|
+
"AppRepository",
|
|
19
|
+
"BaseRepository",
|
|
20
|
+
"CodeInfoRepository",
|
|
21
|
+
"EpicRepository",
|
|
22
|
+
"IntegrationRepository",
|
|
23
|
+
"JourneyRepository",
|
|
24
|
+
"StoryRepository",
|
|
25
|
+
]
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""AcceleratorRepository protocol.
|
|
2
|
+
|
|
3
|
+
Defines the interface for accelerator data access.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Protocol, runtime_checkable
|
|
7
|
+
|
|
8
|
+
from ..models.accelerator import Accelerator
|
|
9
|
+
from .base import BaseRepository
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@runtime_checkable
|
|
13
|
+
class AcceleratorRepository(BaseRepository[Accelerator], Protocol):
|
|
14
|
+
"""Repository protocol for Accelerator entities.
|
|
15
|
+
|
|
16
|
+
Extends BaseRepository with accelerator-specific query methods.
|
|
17
|
+
Accelerators are defined in RST documents and support incremental builds
|
|
18
|
+
via docname tracking.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
async def get_by_status(self, status: str) -> list[Accelerator]:
|
|
22
|
+
"""Get all accelerators with a specific status.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
status: Status to filter by (case-insensitive)
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
List of accelerators with matching status
|
|
29
|
+
"""
|
|
30
|
+
...
|
|
31
|
+
|
|
32
|
+
async def get_by_docname(self, docname: str) -> list[Accelerator]:
|
|
33
|
+
"""Get all accelerators defined in a specific document.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
docname: RST document name
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
List of accelerators from that document
|
|
40
|
+
"""
|
|
41
|
+
...
|
|
42
|
+
|
|
43
|
+
async def clear_by_docname(self, docname: str) -> int:
|
|
44
|
+
"""Remove all accelerators defined in a specific document.
|
|
45
|
+
|
|
46
|
+
Used during incremental builds when a document is re-read.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
docname: RST document name
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Number of accelerators removed
|
|
53
|
+
"""
|
|
54
|
+
...
|
|
55
|
+
|
|
56
|
+
async def get_by_integration(
|
|
57
|
+
self, integration_slug: str, relationship: str
|
|
58
|
+
) -> list[Accelerator]:
|
|
59
|
+
"""Get accelerators that have a relationship with an integration.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
integration_slug: Integration slug to search for
|
|
63
|
+
relationship: Either "sources_from" or "publishes_to"
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
List of accelerators with this integration relationship
|
|
67
|
+
"""
|
|
68
|
+
...
|
|
69
|
+
|
|
70
|
+
async def get_dependents(self, accelerator_slug: str) -> list[Accelerator]:
|
|
71
|
+
"""Get accelerators that depend on a specific accelerator.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
accelerator_slug: Slug of the accelerator to find dependents of
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
List of accelerators that have this accelerator in depends_on
|
|
78
|
+
"""
|
|
79
|
+
...
|
|
80
|
+
|
|
81
|
+
async def get_fed_by(self, accelerator_slug: str) -> list[Accelerator]:
|
|
82
|
+
"""Get accelerators that feed into a specific accelerator.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
accelerator_slug: Slug of the accelerator
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
List of accelerators that have this accelerator in feeds_into
|
|
89
|
+
"""
|
|
90
|
+
...
|
|
91
|
+
|
|
92
|
+
async def get_all_statuses(self) -> set[str]:
|
|
93
|
+
"""Get all unique statuses across all accelerators.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Set of status strings (normalized to lowercase)
|
|
97
|
+
"""
|
|
98
|
+
...
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""AppRepository protocol.
|
|
2
|
+
|
|
3
|
+
Defines the interface for app data access.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Protocol, runtime_checkable
|
|
7
|
+
|
|
8
|
+
from ..models.app import App, AppType
|
|
9
|
+
from .base import BaseRepository
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@runtime_checkable
|
|
13
|
+
class AppRepository(BaseRepository[App], Protocol):
|
|
14
|
+
"""Repository protocol for App entities.
|
|
15
|
+
|
|
16
|
+
Extends BaseRepository with app-specific query methods.
|
|
17
|
+
Apps are indexed from YAML manifests and are read-only during
|
|
18
|
+
a Sphinx build (populated at builder-inited, queried during rendering).
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
async def get_by_type(self, app_type: AppType) -> list[App]:
|
|
22
|
+
"""Get all apps of a specific type.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
app_type: Application type to filter by
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
List of apps matching the type
|
|
29
|
+
"""
|
|
30
|
+
...
|
|
31
|
+
|
|
32
|
+
async def get_by_name(self, name: str) -> App | None:
|
|
33
|
+
"""Get an app by its display name (case-insensitive).
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
name: Display name to search for
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
App if found, None otherwise
|
|
40
|
+
"""
|
|
41
|
+
...
|
|
42
|
+
|
|
43
|
+
async def get_all_types(self) -> set[AppType]:
|
|
44
|
+
"""Get all unique app types that have apps.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Set of app types with at least one app
|
|
48
|
+
"""
|
|
49
|
+
...
|
|
50
|
+
|
|
51
|
+
async def get_apps_with_accelerators(self) -> list[App]:
|
|
52
|
+
"""Get all apps that have accelerators defined.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
List of apps with non-empty accelerators list
|
|
56
|
+
"""
|
|
57
|
+
...
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Base repository protocol for sphinx_hcd.
|
|
2
|
+
|
|
3
|
+
Defines the generic repository interface following julee clean architecture
|
|
4
|
+
patterns. All repository operations are async for consistency with julee,
|
|
5
|
+
with sync adapters provided in the sphinx/ application layer.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Protocol, TypeVar, runtime_checkable
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel
|
|
11
|
+
|
|
12
|
+
T = TypeVar("T", bound=BaseModel)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@runtime_checkable
|
|
16
|
+
class BaseRepository(Protocol[T]):
|
|
17
|
+
"""Generic base repository protocol for HCD entities.
|
|
18
|
+
|
|
19
|
+
This protocol defines the common interface shared by all domain
|
|
20
|
+
repositories in sphinx_hcd. It uses generics to provide type safety
|
|
21
|
+
while eliminating code duplication.
|
|
22
|
+
|
|
23
|
+
Type Parameter:
|
|
24
|
+
T: The domain entity type (must extend Pydantic BaseModel)
|
|
25
|
+
|
|
26
|
+
All methods are async for consistency with julee patterns. The sphinx/
|
|
27
|
+
application layer provides SyncRepositoryAdapter for use in Sphinx
|
|
28
|
+
directives which are synchronous.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
async def get(self, entity_id: str) -> T | None:
|
|
32
|
+
"""Retrieve an entity by ID.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
entity_id: Unique entity identifier (typically a slug)
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Entity if found, None otherwise
|
|
39
|
+
"""
|
|
40
|
+
...
|
|
41
|
+
|
|
42
|
+
async def get_many(self, entity_ids: list[str]) -> dict[str, T | None]:
|
|
43
|
+
"""Retrieve multiple entities by ID.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
entity_ids: List of unique entity identifiers
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Dict mapping entity_id to entity (or None if not found)
|
|
50
|
+
"""
|
|
51
|
+
...
|
|
52
|
+
|
|
53
|
+
async def save(self, entity: T) -> None:
|
|
54
|
+
"""Save an entity.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
entity: Complete entity to save
|
|
58
|
+
|
|
59
|
+
Note:
|
|
60
|
+
Must be idempotent - saving the same entity state is safe.
|
|
61
|
+
"""
|
|
62
|
+
...
|
|
63
|
+
|
|
64
|
+
async def list_all(self) -> list[T]:
|
|
65
|
+
"""List all entities.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
List of all entities in the repository
|
|
69
|
+
"""
|
|
70
|
+
...
|
|
71
|
+
|
|
72
|
+
async def delete(self, entity_id: str) -> bool:
|
|
73
|
+
"""Delete an entity by ID.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
entity_id: Unique entity identifier
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
True if entity was deleted, False if not found
|
|
80
|
+
"""
|
|
81
|
+
...
|
|
82
|
+
|
|
83
|
+
async def clear(self) -> None:
|
|
84
|
+
"""Remove all entities from the repository.
|
|
85
|
+
|
|
86
|
+
Used primarily for testing and re-initialization during
|
|
87
|
+
Sphinx incremental builds.
|
|
88
|
+
"""
|
|
89
|
+
...
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""CodeInfoRepository protocol.
|
|
2
|
+
|
|
3
|
+
Defines the interface for bounded context code introspection data access.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Protocol, runtime_checkable
|
|
7
|
+
|
|
8
|
+
from ..models.code_info import BoundedContextInfo
|
|
9
|
+
from .base import BaseRepository
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@runtime_checkable
|
|
13
|
+
class CodeInfoRepository(BaseRepository[BoundedContextInfo], Protocol):
|
|
14
|
+
"""Repository protocol for BoundedContextInfo entities.
|
|
15
|
+
|
|
16
|
+
Extends BaseRepository with code introspection-specific query methods.
|
|
17
|
+
Code info is populated once at builder-inited by scanning src/ directories.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
async def get_by_code_dir(self, code_dir: str) -> BoundedContextInfo | None:
|
|
21
|
+
"""Get bounded context info by its code directory name.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
code_dir: Directory name in src/ (may differ from slug)
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
BoundedContextInfo if found, None otherwise
|
|
28
|
+
"""
|
|
29
|
+
...
|
|
30
|
+
|
|
31
|
+
async def get_with_entities(self) -> list[BoundedContextInfo]:
|
|
32
|
+
"""Get all bounded contexts that have domain entities.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
List of bounded contexts with at least one entity
|
|
36
|
+
"""
|
|
37
|
+
...
|
|
38
|
+
|
|
39
|
+
async def get_with_use_cases(self) -> list[BoundedContextInfo]:
|
|
40
|
+
"""Get all bounded contexts that have use cases.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
List of bounded contexts with at least one use case
|
|
44
|
+
"""
|
|
45
|
+
...
|
|
46
|
+
|
|
47
|
+
async def get_with_infrastructure(self) -> list[BoundedContextInfo]:
|
|
48
|
+
"""Get all bounded contexts that have infrastructure.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
List of bounded contexts where has_infrastructure is True
|
|
52
|
+
"""
|
|
53
|
+
...
|
|
54
|
+
|
|
55
|
+
async def get_all_entity_names(self) -> set[str]:
|
|
56
|
+
"""Get all unique entity class names across all bounded contexts.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Set of entity class names
|
|
60
|
+
"""
|
|
61
|
+
...
|
|
62
|
+
|
|
63
|
+
async def get_all_use_case_names(self) -> set[str]:
|
|
64
|
+
"""Get all unique use case class names across all bounded contexts.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Set of use case class names
|
|
68
|
+
"""
|
|
69
|
+
...
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""EpicRepository protocol.
|
|
2
|
+
|
|
3
|
+
Defines the interface for epic data access.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Protocol, runtime_checkable
|
|
7
|
+
|
|
8
|
+
from ..models.epic import Epic
|
|
9
|
+
from .base import BaseRepository
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@runtime_checkable
|
|
13
|
+
class EpicRepository(BaseRepository[Epic], Protocol):
|
|
14
|
+
"""Repository protocol for Epic entities.
|
|
15
|
+
|
|
16
|
+
Extends BaseRepository with epic-specific query methods.
|
|
17
|
+
Epics are defined in RST documents and support incremental builds
|
|
18
|
+
via docname tracking.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
async def get_by_docname(self, docname: str) -> list[Epic]:
|
|
22
|
+
"""Get all epics defined in a specific document.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
docname: RST document name
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
List of epics from that document
|
|
29
|
+
"""
|
|
30
|
+
...
|
|
31
|
+
|
|
32
|
+
async def clear_by_docname(self, docname: str) -> int:
|
|
33
|
+
"""Remove all epics defined in a specific document.
|
|
34
|
+
|
|
35
|
+
Used during incremental builds when a document is re-read.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
docname: RST document name
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Number of epics removed
|
|
42
|
+
"""
|
|
43
|
+
...
|
|
44
|
+
|
|
45
|
+
async def get_with_story_ref(self, story_title: str) -> list[Epic]:
|
|
46
|
+
"""Get epics that contain a specific story.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
story_title: Story feature title (case-insensitive)
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
List of epics containing this story in story_refs
|
|
53
|
+
"""
|
|
54
|
+
...
|
|
55
|
+
|
|
56
|
+
async def get_all_story_refs(self) -> set[str]:
|
|
57
|
+
"""Get all unique story references across all epics.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Set of story titles (normalized)
|
|
61
|
+
"""
|
|
62
|
+
...
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""IntegrationRepository protocol.
|
|
2
|
+
|
|
3
|
+
Defines the interface for integration data access.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Protocol, runtime_checkable
|
|
7
|
+
|
|
8
|
+
from ..models.integration import Direction, Integration
|
|
9
|
+
from .base import BaseRepository
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@runtime_checkable
|
|
13
|
+
class IntegrationRepository(BaseRepository[Integration], Protocol):
|
|
14
|
+
"""Repository protocol for Integration entities.
|
|
15
|
+
|
|
16
|
+
Extends BaseRepository with integration-specific query methods.
|
|
17
|
+
Integrations are indexed from YAML manifests and are read-only during
|
|
18
|
+
a Sphinx build (populated at builder-inited, queried during rendering).
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
async def get_by_direction(self, direction: Direction) -> list[Integration]:
|
|
22
|
+
"""Get all integrations with a specific data flow direction.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
direction: Direction to filter by
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
List of integrations matching the direction
|
|
29
|
+
"""
|
|
30
|
+
...
|
|
31
|
+
|
|
32
|
+
async def get_by_module(self, module: str) -> Integration | None:
|
|
33
|
+
"""Get an integration by its module name.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
module: Python module name (e.g., "pilot_data_collection")
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Integration if found, None otherwise
|
|
40
|
+
"""
|
|
41
|
+
...
|
|
42
|
+
|
|
43
|
+
async def get_by_name(self, name: str) -> Integration | None:
|
|
44
|
+
"""Get an integration by its display name (case-insensitive).
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
name: Display name to search for
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Integration if found, None otherwise
|
|
51
|
+
"""
|
|
52
|
+
...
|
|
53
|
+
|
|
54
|
+
async def get_all_directions(self) -> set[Direction]:
|
|
55
|
+
"""Get all unique directions that have integrations.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Set of directions with at least one integration
|
|
59
|
+
"""
|
|
60
|
+
...
|
|
61
|
+
|
|
62
|
+
async def get_with_dependencies(self) -> list[Integration]:
|
|
63
|
+
"""Get all integrations that have external dependencies.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
List of integrations with non-empty depends_on list
|
|
67
|
+
"""
|
|
68
|
+
...
|
|
69
|
+
|
|
70
|
+
async def get_by_dependency(self, dep_name: str) -> list[Integration]:
|
|
71
|
+
"""Get all integrations that depend on a specific external system.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
dep_name: External dependency name (case-insensitive)
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
List of integrations that have this dependency
|
|
78
|
+
"""
|
|
79
|
+
...
|