julee 0.1.4__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/__init__.py +1 -1
- julee/api/tests/routers/test_assembly_specifications.py +2 -0
- julee/api/tests/routers/test_documents.py +2 -0
- julee/api/tests/routers/test_knowledge_service_configs.py +2 -0
- julee/api/tests/routers/test_knowledge_service_queries.py +2 -0
- julee/api/tests/routers/test_system.py +2 -0
- julee/api/tests/routers/test_workflows.py +2 -0
- julee/api/tests/test_app.py +2 -0
- julee/api/tests/test_dependencies.py +2 -0
- julee/api/tests/test_requests.py +2 -0
- julee/contrib/polling/__init__.py +22 -19
- julee/contrib/polling/apps/__init__.py +17 -0
- julee/contrib/polling/apps/worker/__init__.py +17 -0
- julee/contrib/polling/apps/worker/pipelines.py +288 -0
- julee/contrib/polling/domain/__init__.py +7 -9
- julee/contrib/polling/domain/models/__init__.py +6 -7
- julee/contrib/polling/domain/models/polling_config.py +18 -1
- julee/contrib/polling/domain/services/__init__.py +6 -5
- julee/contrib/polling/domain/services/poller.py +1 -1
- julee/contrib/polling/infrastructure/__init__.py +9 -8
- julee/contrib/polling/infrastructure/services/__init__.py +6 -5
- julee/contrib/polling/infrastructure/services/polling/__init__.py +6 -5
- julee/contrib/polling/infrastructure/services/polling/http/__init__.py +6 -5
- julee/contrib/polling/infrastructure/services/polling/http/http_poller_service.py +5 -2
- julee/contrib/polling/infrastructure/temporal/__init__.py +12 -12
- julee/contrib/polling/infrastructure/temporal/activities.py +1 -1
- julee/contrib/polling/infrastructure/temporal/manager.py +291 -0
- julee/contrib/polling/infrastructure/temporal/proxies.py +1 -1
- julee/contrib/polling/tests/unit/apps/worker/test_pipelines.py +580 -0
- julee/contrib/polling/tests/unit/infrastructure/services/polling/http/test_http_poller_service.py +40 -2
- julee/contrib/polling/tests/unit/infrastructure/temporal/__init__.py +7 -0
- julee/contrib/polling/tests/unit/infrastructure/temporal/test_manager.py +475 -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/domain/models/assembly/tests/test_assembly.py +2 -0
- julee/domain/models/assembly_specification/tests/test_assembly_specification.py +2 -0
- julee/domain/models/assembly_specification/tests/test_knowledge_service_query.py +2 -0
- julee/domain/models/custom_fields/tests/test_custom_fields.py +2 -0
- julee/domain/models/document/tests/test_document.py +2 -0
- julee/domain/models/policy/tests/test_document_policy_validation.py +2 -0
- julee/domain/models/policy/tests/test_policy.py +2 -0
- julee/domain/use_cases/tests/test_extract_assemble_data.py +2 -0
- julee/domain/use_cases/tests/test_initialize_system_data.py +2 -0
- julee/domain/use_cases/tests/test_validate_document.py +2 -0
- julee/maintenance/release.py +10 -5
- julee/repositories/memory/tests/test_document.py +2 -0
- julee/repositories/memory/tests/test_document_policy_validation.py +2 -0
- julee/repositories/memory/tests/test_policy.py +2 -0
- julee/repositories/minio/tests/test_assembly.py +2 -0
- julee/repositories/minio/tests/test_assembly_specification.py +2 -0
- julee/repositories/minio/tests/test_client_protocol.py +3 -0
- julee/repositories/minio/tests/test_document.py +2 -0
- julee/repositories/minio/tests/test_document_policy_validation.py +2 -0
- julee/repositories/minio/tests/test_knowledge_service_config.py +2 -0
- julee/repositories/minio/tests/test_knowledge_service_query.py +2 -0
- julee/repositories/minio/tests/test_policy.py +2 -0
- julee/services/knowledge_service/anthropic/tests/test_knowledge_service.py +2 -0
- julee/services/knowledge_service/memory/test_knowledge_service.py +2 -0
- julee/services/knowledge_service/test_factory.py +2 -0
- julee/util/tests/test_decorators.py +2 -0
- julee-0.1.6.dist-info/METADATA +104 -0
- julee-0.1.6.dist-info/RECORD +288 -0
- 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.4.dist-info/METADATA +0 -197
- julee-0.1.4.dist-info/RECORD +0 -196
- {julee-0.1.4.dist-info → julee-0.1.6.dist-info}/WHEEL +0 -0
- {julee-0.1.4.dist-info → julee-0.1.6.dist-info}/licenses/LICENSE +0 -0
- {julee-0.1.4.dist-info → julee-0.1.6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Memory implementation of AcceleratorRepository."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from ...domain.models.accelerator import Accelerator
|
|
6
|
+
from ...domain.repositories.accelerator import AcceleratorRepository
|
|
7
|
+
from .base import MemoryRepositoryMixin
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MemoryAcceleratorRepository(
|
|
13
|
+
MemoryRepositoryMixin[Accelerator], AcceleratorRepository
|
|
14
|
+
):
|
|
15
|
+
"""In-memory implementation of AcceleratorRepository.
|
|
16
|
+
|
|
17
|
+
Accelerators are stored in a dictionary keyed by slug. This implementation
|
|
18
|
+
is used during Sphinx builds where accelerators are populated during doctree
|
|
19
|
+
processing and support incremental builds via docname tracking.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self) -> None:
|
|
23
|
+
"""Initialize with empty storage."""
|
|
24
|
+
self.storage: dict[str, Accelerator] = {}
|
|
25
|
+
self.entity_name = "Accelerator"
|
|
26
|
+
self.id_field = "slug"
|
|
27
|
+
|
|
28
|
+
async def get_by_status(self, status: str) -> list[Accelerator]:
|
|
29
|
+
"""Get all accelerators with a specific status."""
|
|
30
|
+
status_normalized = status.lower().strip()
|
|
31
|
+
return [
|
|
32
|
+
accel
|
|
33
|
+
for accel in self.storage.values()
|
|
34
|
+
if accel.status_normalized == status_normalized
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
async def get_by_docname(self, docname: str) -> list[Accelerator]:
|
|
38
|
+
"""Get all accelerators defined in a specific document."""
|
|
39
|
+
return [accel for accel in self.storage.values() if accel.docname == docname]
|
|
40
|
+
|
|
41
|
+
async def clear_by_docname(self, docname: str) -> int:
|
|
42
|
+
"""Remove all accelerators defined in a specific document."""
|
|
43
|
+
to_remove = [
|
|
44
|
+
slug for slug, accel in self.storage.items() if accel.docname == docname
|
|
45
|
+
]
|
|
46
|
+
for slug in to_remove:
|
|
47
|
+
del self.storage[slug]
|
|
48
|
+
return len(to_remove)
|
|
49
|
+
|
|
50
|
+
async def get_by_integration(
|
|
51
|
+
self, integration_slug: str, relationship: str
|
|
52
|
+
) -> list[Accelerator]:
|
|
53
|
+
"""Get accelerators that have a relationship with an integration."""
|
|
54
|
+
result = []
|
|
55
|
+
for accel in self.storage.values():
|
|
56
|
+
if relationship == "sources_from":
|
|
57
|
+
if integration_slug in accel.get_sources_from_slugs():
|
|
58
|
+
result.append(accel)
|
|
59
|
+
elif relationship == "publishes_to":
|
|
60
|
+
if integration_slug in accel.get_publishes_to_slugs():
|
|
61
|
+
result.append(accel)
|
|
62
|
+
return result
|
|
63
|
+
|
|
64
|
+
async def get_dependents(self, accelerator_slug: str) -> list[Accelerator]:
|
|
65
|
+
"""Get accelerators that depend on a specific accelerator."""
|
|
66
|
+
return [
|
|
67
|
+
accel
|
|
68
|
+
for accel in self.storage.values()
|
|
69
|
+
if accelerator_slug in accel.depends_on
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
async def get_fed_by(self, accelerator_slug: str) -> list[Accelerator]:
|
|
73
|
+
"""Get accelerators that feed into a specific accelerator."""
|
|
74
|
+
return [
|
|
75
|
+
accel
|
|
76
|
+
for accel in self.storage.values()
|
|
77
|
+
if accelerator_slug in accel.feeds_into
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
async def get_all_statuses(self) -> set[str]:
|
|
81
|
+
"""Get all unique statuses across all accelerators."""
|
|
82
|
+
return {
|
|
83
|
+
accel.status_normalized
|
|
84
|
+
for accel in self.storage.values()
|
|
85
|
+
if accel.status_normalized
|
|
86
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Memory implementation of AppRepository."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from ...domain.models.app import App, AppType
|
|
6
|
+
from ...domain.repositories.app import AppRepository
|
|
7
|
+
from ...utils import normalize_name
|
|
8
|
+
from .base import MemoryRepositoryMixin
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MemoryAppRepository(MemoryRepositoryMixin[App], AppRepository):
|
|
14
|
+
"""In-memory implementation of AppRepository.
|
|
15
|
+
|
|
16
|
+
Apps are stored in a dictionary keyed by slug. This implementation
|
|
17
|
+
is used during Sphinx builds where apps 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, App] = {}
|
|
24
|
+
self.entity_name = "App"
|
|
25
|
+
self.id_field = "slug"
|
|
26
|
+
|
|
27
|
+
async def get_by_type(self, app_type: AppType) -> list[App]:
|
|
28
|
+
"""Get all apps of a specific type."""
|
|
29
|
+
return [app for app in self.storage.values() if app.app_type == app_type]
|
|
30
|
+
|
|
31
|
+
async def get_by_name(self, name: str) -> App | None:
|
|
32
|
+
"""Get an app by its display name (case-insensitive)."""
|
|
33
|
+
name_normalized = normalize_name(name)
|
|
34
|
+
for app in self.storage.values():
|
|
35
|
+
if app.name_normalized == name_normalized:
|
|
36
|
+
return app
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
async def get_all_types(self) -> set[AppType]:
|
|
40
|
+
"""Get all unique app types that have apps."""
|
|
41
|
+
return {app.app_type for app in self.storage.values()}
|
|
42
|
+
|
|
43
|
+
async def get_apps_with_accelerators(self) -> list[App]:
|
|
44
|
+
"""Get all apps that have accelerators defined."""
|
|
45
|
+
return [app for app in self.storage.values() if app.accelerators]
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Memory repository base classes and mixins for sphinx_hcd.
|
|
2
|
+
|
|
3
|
+
Provides common functionality for in-memory repository implementations,
|
|
4
|
+
following julee patterns but simplified for sphinx_hcd's needs.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Any, Generic, TypeVar
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel
|
|
11
|
+
|
|
12
|
+
T = TypeVar("T", bound=BaseModel)
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MemoryRepositoryMixin(Generic[T]):
|
|
18
|
+
"""Mixin providing common repository patterns for memory implementations.
|
|
19
|
+
|
|
20
|
+
Encapsulates common functionality used across all memory repository
|
|
21
|
+
implementations:
|
|
22
|
+
- Dictionary-based entity storage and retrieval
|
|
23
|
+
- Standardized logging patterns
|
|
24
|
+
- Generic CRUD operations
|
|
25
|
+
|
|
26
|
+
Classes using this mixin must provide:
|
|
27
|
+
- self.storage: dict[str, T] for entity storage
|
|
28
|
+
- self.entity_name: str for logging
|
|
29
|
+
- self.id_field: str naming the entity's ID field
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
storage: dict[str, T]
|
|
33
|
+
entity_name: str
|
|
34
|
+
id_field: str
|
|
35
|
+
|
|
36
|
+
def _get_entity_id(self, entity: T) -> str:
|
|
37
|
+
"""Extract the entity ID from an entity instance."""
|
|
38
|
+
return getattr(entity, self.id_field)
|
|
39
|
+
|
|
40
|
+
async def get(self, entity_id: str) -> T | None:
|
|
41
|
+
"""Retrieve an entity by ID."""
|
|
42
|
+
entity = self.storage.get(entity_id)
|
|
43
|
+
if entity is None:
|
|
44
|
+
logger.debug(
|
|
45
|
+
f"Memory{self.entity_name}Repository: {self.entity_name} not found",
|
|
46
|
+
extra={f"{self.entity_name.lower()}_id": entity_id},
|
|
47
|
+
)
|
|
48
|
+
return entity
|
|
49
|
+
|
|
50
|
+
async def get_many(self, entity_ids: list[str]) -> dict[str, T | None]:
|
|
51
|
+
"""Retrieve multiple entities by ID."""
|
|
52
|
+
result: dict[str, T | None] = {}
|
|
53
|
+
for entity_id in entity_ids:
|
|
54
|
+
result[entity_id] = self.storage.get(entity_id)
|
|
55
|
+
return result
|
|
56
|
+
|
|
57
|
+
async def save(self, entity: T) -> None:
|
|
58
|
+
"""Save an entity to storage."""
|
|
59
|
+
entity_id = self._get_entity_id(entity)
|
|
60
|
+
self.storage[entity_id] = entity
|
|
61
|
+
logger.debug(
|
|
62
|
+
f"Memory{self.entity_name}Repository: Saved {self.entity_name}",
|
|
63
|
+
extra={f"{self.entity_name.lower()}_id": entity_id},
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
async def list_all(self) -> list[T]:
|
|
67
|
+
"""List all entities."""
|
|
68
|
+
return list(self.storage.values())
|
|
69
|
+
|
|
70
|
+
async def delete(self, entity_id: str) -> bool:
|
|
71
|
+
"""Delete an entity by ID."""
|
|
72
|
+
if entity_id in self.storage:
|
|
73
|
+
del self.storage[entity_id]
|
|
74
|
+
logger.debug(
|
|
75
|
+
f"Memory{self.entity_name}Repository: Deleted {self.entity_name}",
|
|
76
|
+
extra={f"{self.entity_name.lower()}_id": entity_id},
|
|
77
|
+
)
|
|
78
|
+
return True
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
async def clear(self) -> None:
|
|
82
|
+
"""Remove all entities from storage."""
|
|
83
|
+
count = len(self.storage)
|
|
84
|
+
self.storage.clear()
|
|
85
|
+
logger.debug(
|
|
86
|
+
f"Memory{self.entity_name}Repository: Cleared {count} entities",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Additional query methods that subclasses can use
|
|
90
|
+
|
|
91
|
+
async def find_by_field(self, field: str, value: Any) -> list[T]:
|
|
92
|
+
"""Find all entities where field equals value."""
|
|
93
|
+
return [
|
|
94
|
+
entity
|
|
95
|
+
for entity in self.storage.values()
|
|
96
|
+
if getattr(entity, field, None) == value
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
async def find_by_field_in(self, field: str, values: list[Any]) -> list[T]:
|
|
100
|
+
"""Find all entities where field is in values."""
|
|
101
|
+
value_set = set(values)
|
|
102
|
+
return [
|
|
103
|
+
entity
|
|
104
|
+
for entity in self.storage.values()
|
|
105
|
+
if getattr(entity, field, None) in value_set
|
|
106
|
+
]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Memory implementation of CodeInfoRepository."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from ...domain.models.code_info import BoundedContextInfo
|
|
6
|
+
from ...domain.repositories.code_info import CodeInfoRepository
|
|
7
|
+
from .base import MemoryRepositoryMixin
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MemoryCodeInfoRepository(
|
|
13
|
+
MemoryRepositoryMixin[BoundedContextInfo], CodeInfoRepository
|
|
14
|
+
):
|
|
15
|
+
"""In-memory implementation of CodeInfoRepository.
|
|
16
|
+
|
|
17
|
+
Bounded context info is stored in a dictionary keyed by slug. This implementation
|
|
18
|
+
is used during Sphinx builds where code info is populated at builder-inited
|
|
19
|
+
by scanning src/ directories.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self) -> None:
|
|
23
|
+
"""Initialize with empty storage."""
|
|
24
|
+
self.storage: dict[str, BoundedContextInfo] = {}
|
|
25
|
+
self.entity_name = "BoundedContextInfo"
|
|
26
|
+
self.id_field = "slug"
|
|
27
|
+
|
|
28
|
+
async def get_by_code_dir(self, code_dir: str) -> BoundedContextInfo | None:
|
|
29
|
+
"""Get bounded context info by its code directory name."""
|
|
30
|
+
for info in self.storage.values():
|
|
31
|
+
if info.code_dir == code_dir:
|
|
32
|
+
return info
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
async def get_with_entities(self) -> list[BoundedContextInfo]:
|
|
36
|
+
"""Get all bounded contexts that have domain entities."""
|
|
37
|
+
return [info for info in self.storage.values() if info.has_entities]
|
|
38
|
+
|
|
39
|
+
async def get_with_use_cases(self) -> list[BoundedContextInfo]:
|
|
40
|
+
"""Get all bounded contexts that have use cases."""
|
|
41
|
+
return [info for info in self.storage.values() if info.has_use_cases]
|
|
42
|
+
|
|
43
|
+
async def get_with_infrastructure(self) -> list[BoundedContextInfo]:
|
|
44
|
+
"""Get all bounded contexts that have infrastructure."""
|
|
45
|
+
return [info for info in self.storage.values() if info.has_infrastructure]
|
|
46
|
+
|
|
47
|
+
async def get_all_entity_names(self) -> set[str]:
|
|
48
|
+
"""Get all unique entity class names across all bounded contexts."""
|
|
49
|
+
names: set[str] = set()
|
|
50
|
+
for info in self.storage.values():
|
|
51
|
+
names.update(info.get_entity_names())
|
|
52
|
+
return names
|
|
53
|
+
|
|
54
|
+
async def get_all_use_case_names(self) -> set[str]:
|
|
55
|
+
"""Get all unique use case class names across all bounded contexts."""
|
|
56
|
+
names: set[str] = set()
|
|
57
|
+
for info in self.storage.values():
|
|
58
|
+
names.update(info.get_use_case_names())
|
|
59
|
+
return names
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Memory implementation of EpicRepository."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from ...domain.models.epic import Epic
|
|
6
|
+
from ...domain.repositories.epic import EpicRepository
|
|
7
|
+
from ...utils import normalize_name
|
|
8
|
+
from .base import MemoryRepositoryMixin
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MemoryEpicRepository(MemoryRepositoryMixin[Epic], EpicRepository):
|
|
14
|
+
"""In-memory implementation of EpicRepository.
|
|
15
|
+
|
|
16
|
+
Epics are stored in a dictionary keyed by slug. This implementation
|
|
17
|
+
is used during Sphinx builds where epics 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, Epic] = {}
|
|
24
|
+
self.entity_name = "Epic"
|
|
25
|
+
self.id_field = "slug"
|
|
26
|
+
|
|
27
|
+
async def get_by_docname(self, docname: str) -> list[Epic]:
|
|
28
|
+
"""Get all epics defined in a specific document."""
|
|
29
|
+
return [epic for epic in self.storage.values() if epic.docname == docname]
|
|
30
|
+
|
|
31
|
+
async def clear_by_docname(self, docname: str) -> int:
|
|
32
|
+
"""Remove all epics defined in a specific document."""
|
|
33
|
+
to_remove = [
|
|
34
|
+
slug for slug, epic in self.storage.items() if epic.docname == docname
|
|
35
|
+
]
|
|
36
|
+
for slug in to_remove:
|
|
37
|
+
del self.storage[slug]
|
|
38
|
+
return len(to_remove)
|
|
39
|
+
|
|
40
|
+
async def get_with_story_ref(self, story_title: str) -> list[Epic]:
|
|
41
|
+
"""Get epics that contain a specific story."""
|
|
42
|
+
story_normalized = normalize_name(story_title)
|
|
43
|
+
return [
|
|
44
|
+
epic
|
|
45
|
+
for epic in self.storage.values()
|
|
46
|
+
if any(normalize_name(ref) == story_normalized for ref in epic.story_refs)
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
async def get_all_story_refs(self) -> set[str]:
|
|
50
|
+
"""Get all unique story references across all epics."""
|
|
51
|
+
refs: set[str] = set()
|
|
52
|
+
for epic in self.storage.values():
|
|
53
|
+
refs.update(normalize_name(ref) for ref in epic.story_refs)
|
|
54
|
+
return refs
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Memory implementation of IntegrationRepository."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from ...domain.models.integration import Direction, Integration
|
|
6
|
+
from ...domain.repositories.integration import IntegrationRepository
|
|
7
|
+
from ...utils import normalize_name
|
|
8
|
+
from .base import MemoryRepositoryMixin
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MemoryIntegrationRepository(
|
|
14
|
+
MemoryRepositoryMixin[Integration], IntegrationRepository
|
|
15
|
+
):
|
|
16
|
+
"""In-memory implementation of IntegrationRepository.
|
|
17
|
+
|
|
18
|
+
Integrations are stored in a dictionary keyed by slug. This implementation
|
|
19
|
+
is used during Sphinx builds where integrations are populated at builder-inited
|
|
20
|
+
and queried during doctree processing.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self) -> None:
|
|
24
|
+
"""Initialize with empty storage."""
|
|
25
|
+
self.storage: dict[str, Integration] = {}
|
|
26
|
+
self.entity_name = "Integration"
|
|
27
|
+
self.id_field = "slug"
|
|
28
|
+
|
|
29
|
+
async def get_by_direction(self, direction: Direction) -> list[Integration]:
|
|
30
|
+
"""Get all integrations with a specific direction."""
|
|
31
|
+
return [
|
|
32
|
+
integration
|
|
33
|
+
for integration in self.storage.values()
|
|
34
|
+
if integration.direction == direction
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
async def get_by_module(self, module: str) -> Integration | None:
|
|
38
|
+
"""Get an integration by its module name."""
|
|
39
|
+
for integration in self.storage.values():
|
|
40
|
+
if integration.module == module:
|
|
41
|
+
return integration
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
async def get_by_name(self, name: str) -> Integration | None:
|
|
45
|
+
"""Get an integration by its display name (case-insensitive)."""
|
|
46
|
+
name_normalized = normalize_name(name)
|
|
47
|
+
for integration in self.storage.values():
|
|
48
|
+
if integration.name_normalized == name_normalized:
|
|
49
|
+
return integration
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
async def get_all_directions(self) -> set[Direction]:
|
|
53
|
+
"""Get all unique directions that have integrations."""
|
|
54
|
+
return {integration.direction for integration in self.storage.values()}
|
|
55
|
+
|
|
56
|
+
async def get_with_dependencies(self) -> list[Integration]:
|
|
57
|
+
"""Get all integrations that have external dependencies."""
|
|
58
|
+
return [
|
|
59
|
+
integration
|
|
60
|
+
for integration in self.storage.values()
|
|
61
|
+
if integration.depends_on
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
async def get_by_dependency(self, dep_name: str) -> list[Integration]:
|
|
65
|
+
"""Get all integrations that depend on a specific external system."""
|
|
66
|
+
return [
|
|
67
|
+
integration
|
|
68
|
+
for integration in self.storage.values()
|
|
69
|
+
if integration.has_dependency(dep_name)
|
|
70
|
+
]
|
|
@@ -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
|
+
]
|