julee 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. julee/__init__.py +3 -0
  2. julee/api/__init__.py +20 -0
  3. julee/api/app.py +180 -0
  4. julee/api/dependencies.py +257 -0
  5. julee/api/requests.py +175 -0
  6. julee/api/responses.py +43 -0
  7. julee/api/routers/__init__.py +43 -0
  8. julee/api/routers/assembly_specifications.py +212 -0
  9. julee/api/routers/documents.py +182 -0
  10. julee/api/routers/knowledge_service_configs.py +79 -0
  11. julee/api/routers/knowledge_service_queries.py +293 -0
  12. julee/api/routers/system.py +137 -0
  13. julee/api/routers/workflows.py +234 -0
  14. julee/api/services/__init__.py +20 -0
  15. julee/api/services/system_initialization.py +214 -0
  16. julee/api/tests/__init__.py +14 -0
  17. julee/api/tests/routers/__init__.py +17 -0
  18. julee/api/tests/routers/test_assembly_specifications.py +749 -0
  19. julee/api/tests/routers/test_documents.py +301 -0
  20. julee/api/tests/routers/test_knowledge_service_configs.py +234 -0
  21. julee/api/tests/routers/test_knowledge_service_queries.py +738 -0
  22. julee/api/tests/routers/test_system.py +179 -0
  23. julee/api/tests/routers/test_workflows.py +393 -0
  24. julee/api/tests/test_app.py +285 -0
  25. julee/api/tests/test_dependencies.py +245 -0
  26. julee/api/tests/test_requests.py +250 -0
  27. julee/domain/__init__.py +22 -0
  28. julee/domain/models/__init__.py +49 -0
  29. julee/domain/models/assembly/__init__.py +17 -0
  30. julee/domain/models/assembly/assembly.py +103 -0
  31. julee/domain/models/assembly/tests/__init__.py +0 -0
  32. julee/domain/models/assembly/tests/factories.py +37 -0
  33. julee/domain/models/assembly/tests/test_assembly.py +430 -0
  34. julee/domain/models/assembly_specification/__init__.py +24 -0
  35. julee/domain/models/assembly_specification/assembly_specification.py +172 -0
  36. julee/domain/models/assembly_specification/knowledge_service_query.py +123 -0
  37. julee/domain/models/assembly_specification/tests/__init__.py +0 -0
  38. julee/domain/models/assembly_specification/tests/factories.py +78 -0
  39. julee/domain/models/assembly_specification/tests/test_assembly_specification.py +490 -0
  40. julee/domain/models/assembly_specification/tests/test_knowledge_service_query.py +310 -0
  41. julee/domain/models/custom_fields/__init__.py +0 -0
  42. julee/domain/models/custom_fields/content_stream.py +68 -0
  43. julee/domain/models/custom_fields/tests/__init__.py +0 -0
  44. julee/domain/models/custom_fields/tests/test_custom_fields.py +53 -0
  45. julee/domain/models/document/__init__.py +17 -0
  46. julee/domain/models/document/document.py +150 -0
  47. julee/domain/models/document/tests/__init__.py +0 -0
  48. julee/domain/models/document/tests/factories.py +76 -0
  49. julee/domain/models/document/tests/test_document.py +297 -0
  50. julee/domain/models/knowledge_service_config/__init__.py +17 -0
  51. julee/domain/models/knowledge_service_config/knowledge_service_config.py +86 -0
  52. julee/domain/models/policy/__init__.py +15 -0
  53. julee/domain/models/policy/document_policy_validation.py +220 -0
  54. julee/domain/models/policy/policy.py +203 -0
  55. julee/domain/models/policy/tests/__init__.py +0 -0
  56. julee/domain/models/policy/tests/factories.py +47 -0
  57. julee/domain/models/policy/tests/test_document_policy_validation.py +420 -0
  58. julee/domain/models/policy/tests/test_policy.py +546 -0
  59. julee/domain/repositories/__init__.py +27 -0
  60. julee/domain/repositories/assembly.py +45 -0
  61. julee/domain/repositories/assembly_specification.py +52 -0
  62. julee/domain/repositories/base.py +146 -0
  63. julee/domain/repositories/document.py +49 -0
  64. julee/domain/repositories/document_policy_validation.py +52 -0
  65. julee/domain/repositories/knowledge_service_config.py +54 -0
  66. julee/domain/repositories/knowledge_service_query.py +44 -0
  67. julee/domain/repositories/policy.py +49 -0
  68. julee/domain/use_cases/__init__.py +17 -0
  69. julee/domain/use_cases/decorators.py +107 -0
  70. julee/domain/use_cases/extract_assemble_data.py +649 -0
  71. julee/domain/use_cases/initialize_system_data.py +842 -0
  72. julee/domain/use_cases/tests/__init__.py +7 -0
  73. julee/domain/use_cases/tests/test_extract_assemble_data.py +548 -0
  74. julee/domain/use_cases/tests/test_initialize_system_data.py +455 -0
  75. julee/domain/use_cases/tests/test_validate_document.py +1228 -0
  76. julee/domain/use_cases/validate_document.py +736 -0
  77. julee/fixtures/assembly_specifications.yaml +70 -0
  78. julee/fixtures/documents.yaml +178 -0
  79. julee/fixtures/knowledge_service_configs.yaml +37 -0
  80. julee/fixtures/knowledge_service_queries.yaml +27 -0
  81. julee/repositories/__init__.py +17 -0
  82. julee/repositories/memory/__init__.py +31 -0
  83. julee/repositories/memory/assembly.py +84 -0
  84. julee/repositories/memory/assembly_specification.py +125 -0
  85. julee/repositories/memory/base.py +227 -0
  86. julee/repositories/memory/document.py +149 -0
  87. julee/repositories/memory/document_policy_validation.py +104 -0
  88. julee/repositories/memory/knowledge_service_config.py +123 -0
  89. julee/repositories/memory/knowledge_service_query.py +120 -0
  90. julee/repositories/memory/policy.py +87 -0
  91. julee/repositories/memory/tests/__init__.py +0 -0
  92. julee/repositories/memory/tests/test_document.py +212 -0
  93. julee/repositories/memory/tests/test_document_policy_validation.py +161 -0
  94. julee/repositories/memory/tests/test_policy.py +443 -0
  95. julee/repositories/minio/__init__.py +31 -0
  96. julee/repositories/minio/assembly.py +103 -0
  97. julee/repositories/minio/assembly_specification.py +170 -0
  98. julee/repositories/minio/client.py +570 -0
  99. julee/repositories/minio/document.py +530 -0
  100. julee/repositories/minio/document_policy_validation.py +120 -0
  101. julee/repositories/minio/knowledge_service_config.py +187 -0
  102. julee/repositories/minio/knowledge_service_query.py +211 -0
  103. julee/repositories/minio/policy.py +106 -0
  104. julee/repositories/minio/tests/__init__.py +0 -0
  105. julee/repositories/minio/tests/fake_client.py +213 -0
  106. julee/repositories/minio/tests/test_assembly.py +374 -0
  107. julee/repositories/minio/tests/test_assembly_specification.py +391 -0
  108. julee/repositories/minio/tests/test_client_protocol.py +57 -0
  109. julee/repositories/minio/tests/test_document.py +591 -0
  110. julee/repositories/minio/tests/test_document_policy_validation.py +192 -0
  111. julee/repositories/minio/tests/test_knowledge_service_config.py +374 -0
  112. julee/repositories/minio/tests/test_knowledge_service_query.py +438 -0
  113. julee/repositories/minio/tests/test_policy.py +559 -0
  114. julee/repositories/temporal/__init__.py +38 -0
  115. julee/repositories/temporal/activities.py +114 -0
  116. julee/repositories/temporal/activity_names.py +34 -0
  117. julee/repositories/temporal/proxies.py +159 -0
  118. julee/services/__init__.py +18 -0
  119. julee/services/knowledge_service/__init__.py +48 -0
  120. julee/services/knowledge_service/anthropic/__init__.py +12 -0
  121. julee/services/knowledge_service/anthropic/knowledge_service.py +331 -0
  122. julee/services/knowledge_service/anthropic/tests/test_knowledge_service.py +318 -0
  123. julee/services/knowledge_service/factory.py +138 -0
  124. julee/services/knowledge_service/knowledge_service.py +160 -0
  125. julee/services/knowledge_service/memory/__init__.py +13 -0
  126. julee/services/knowledge_service/memory/knowledge_service.py +278 -0
  127. julee/services/knowledge_service/memory/test_knowledge_service.py +345 -0
  128. julee/services/knowledge_service/test_factory.py +112 -0
  129. julee/services/temporal/__init__.py +38 -0
  130. julee/services/temporal/activities.py +86 -0
  131. julee/services/temporal/activity_names.py +22 -0
  132. julee/services/temporal/proxies.py +41 -0
  133. julee/util/__init__.py +0 -0
  134. julee/util/domain.py +119 -0
  135. julee/util/repos/__init__.py +0 -0
  136. julee/util/repos/minio/__init__.py +0 -0
  137. julee/util/repos/minio/file_storage.py +213 -0
  138. julee/util/repos/temporal/__init__.py +11 -0
  139. julee/util/repos/temporal/client_proxies/file_storage.py +68 -0
  140. julee/util/repos/temporal/data_converter.py +123 -0
  141. julee/util/repos/temporal/minio_file_storage.py +12 -0
  142. julee/util/repos/temporal/proxies/__init__.py +0 -0
  143. julee/util/repos/temporal/proxies/file_storage.py +58 -0
  144. julee/util/repositories.py +55 -0
  145. julee/util/temporal/__init__.py +22 -0
  146. julee/util/temporal/activities.py +123 -0
  147. julee/util/temporal/decorators.py +473 -0
  148. julee/util/tests/__init__.py +1 -0
  149. julee/util/tests/test_decorators.py +770 -0
  150. julee/util/validation/__init__.py +29 -0
  151. julee/util/validation/repository.py +100 -0
  152. julee/util/validation/type_guards.py +369 -0
  153. julee/worker.py +211 -0
  154. julee/workflows/__init__.py +26 -0
  155. julee/workflows/extract_assemble.py +215 -0
  156. julee/workflows/validate_document.py +228 -0
  157. julee-0.1.0.dist-info/METADATA +195 -0
  158. julee-0.1.0.dist-info/RECORD +161 -0
  159. julee-0.1.0.dist-info/WHEEL +5 -0
  160. julee-0.1.0.dist-info/licenses/LICENSE +674 -0
  161. julee-0.1.0.dist-info/top_level.txt +1 -0
julee/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """Julee - Clean architecture for accountable and transparent digital supply chains."""
2
+
3
+ __version__ = "0.1.0"
julee/api/__init__.py ADDED
@@ -0,0 +1,20 @@
1
+ """
2
+ FastAPI interface adapters for the julee CEAP workflow system.
3
+
4
+ This package contains the HTTP API layer that provides external access to the
5
+ CEAP (Capture, Extract, Assemble, Publish) workflow functionality.
6
+
7
+ The API follows clean architecture patterns:
8
+ - Request models for external client contracts (API-specific validation)
9
+ - Domain models returned directly as responses (no wrapper models needed)
10
+ - Dependency injection for use cases and repositories
11
+ - HTTPException for error responses with appropriate status codes
12
+
13
+ Modules:
14
+ - requests: Pydantic models for API request validation
15
+ - responses: Minimal API-specific response models (health checks, etc.)
16
+ - app: FastAPI application setup and endpoint definitions
17
+ - dependencies: Dependency injection configuration
18
+ """
19
+
20
+ __all__: list[str] = []
julee/api/app.py ADDED
@@ -0,0 +1,180 @@
1
+ """
2
+ FastAPI application for julee CEAP workflow system.
3
+
4
+ This module provides the HTTP API layer for the Capture, Extract, Assemble,
5
+ Publish workflow system. It follows clean architecture principles with
6
+ proper dependency injection and error handling.
7
+
8
+ The API provides endpoints for:
9
+ - Knowledge service queries (CRUD operations)
10
+ - Assembly specifications (CRUD operations)
11
+ - Health checks and system status
12
+
13
+ All endpoints use domain models for responses and follow RESTful conventions
14
+ with proper HTTP status codes and error handling.
15
+ """
16
+
17
+ import logging
18
+ import uvicorn
19
+ from contextlib import asynccontextmanager
20
+ from typing import AsyncGenerator, Any, Callable
21
+
22
+ from fastapi import FastAPI
23
+ from fastapi.middleware.cors import CORSMiddleware
24
+ from fastapi_pagination import add_pagination
25
+ from fastapi_pagination.utils import disable_installed_extensions_check
26
+
27
+ from julee.api.routers import (
28
+ assembly_specifications_router,
29
+ knowledge_service_queries_router,
30
+ knowledge_service_configs_router,
31
+ system_router,
32
+ documents_router,
33
+ workflows_router,
34
+ )
35
+ from julee.api.dependencies import (
36
+ get_startup_dependencies,
37
+ get_knowledge_service_config_repository,
38
+ )
39
+
40
+ # Disable pagination extensions check for cleaner startup
41
+ disable_installed_extensions_check()
42
+
43
+ logger = logging.getLogger(__name__)
44
+
45
+
46
+ def setup_logging() -> None:
47
+ """Configure logging for the application."""
48
+ logging.basicConfig(
49
+ level=logging.INFO,
50
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
51
+ handlers=[
52
+ logging.StreamHandler(),
53
+ ],
54
+ )
55
+
56
+ # Set specific log levels
57
+ logging.getLogger("julee").setLevel(logging.DEBUG)
58
+ logging.getLogger("fastapi").setLevel(logging.INFO)
59
+ logging.getLogger("uvicorn").setLevel(logging.INFO)
60
+
61
+
62
+ # Setup logging
63
+ setup_logging()
64
+
65
+
66
+ def resolve_dependency(app: FastAPI, dependency_func: Callable[[], Any]) -> Any:
67
+ """Resolve a dependency, respecting test overrides."""
68
+ override = app.dependency_overrides.get(dependency_func)
69
+ return override() if override else dependency_func()
70
+
71
+
72
+ @asynccontextmanager
73
+ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
74
+ """Lifespan context manager for application startup and shutdown."""
75
+ # Startup
76
+ logger.info("Starting application initialization")
77
+
78
+ try:
79
+ # Check if we're in test mode by looking for repository overrides
80
+ if get_knowledge_service_config_repository in app.dependency_overrides:
81
+ logger.info("Test mode detected, skipping system initialization")
82
+ else:
83
+ # Normal production initialization
84
+ startup_deps = await resolve_dependency(app, get_startup_dependencies)
85
+ service = await startup_deps.get_system_initialization_service()
86
+
87
+ # Execute initialization
88
+ results = await service.initialize()
89
+
90
+ logger.info(
91
+ "Application initialization completed successfully",
92
+ extra={
93
+ "initialization_results": results,
94
+ "tasks_completed": results.get("tasks_completed", []),
95
+ },
96
+ )
97
+
98
+ except Exception as e:
99
+ logger.error(
100
+ "Application initialization failed",
101
+ exc_info=True,
102
+ extra={
103
+ "error_type": type(e).__name__,
104
+ "error_message": str(e),
105
+ },
106
+ )
107
+ # Re-raise to prevent application startup if critical init fails
108
+ raise
109
+
110
+ yield
111
+
112
+ # Shutdown (if needed)
113
+ logger.info("Application shutdown")
114
+
115
+
116
+ # Create FastAPI app
117
+ app = FastAPI(
118
+ title="Julee Example CEAP API",
119
+ description="API for the Capture, Extract, Assemble, Publish workflow",
120
+ version="0.1.0",
121
+ docs_url="/docs",
122
+ redoc_url="/redoc",
123
+ lifespan=lifespan,
124
+ )
125
+
126
+ # Add CORS middleware
127
+ app.add_middleware(
128
+ CORSMiddleware,
129
+ allow_origins=["*"], # Configure appropriately for production
130
+ allow_credentials=True,
131
+ allow_methods=["*"],
132
+ allow_headers=["*"],
133
+ )
134
+
135
+ # Add pagination support
136
+ _ = add_pagination(app)
137
+
138
+
139
+ # Include routers
140
+ app.include_router(system_router, tags=["System"])
141
+
142
+ app.include_router(
143
+ knowledge_service_queries_router,
144
+ prefix="/knowledge_service_queries",
145
+ tags=["Knowledge Service Queries"],
146
+ )
147
+
148
+ app.include_router(
149
+ knowledge_service_configs_router,
150
+ prefix="/knowledge_service_configs",
151
+ tags=["Knowledge Service Configs"],
152
+ )
153
+
154
+ app.include_router(
155
+ assembly_specifications_router,
156
+ prefix="/assembly_specifications",
157
+ tags=["Assembly Specifications"],
158
+ )
159
+
160
+ app.include_router(
161
+ documents_router,
162
+ prefix="/documents",
163
+ tags=["Documents"],
164
+ )
165
+
166
+ app.include_router(
167
+ workflows_router,
168
+ prefix="/workflows",
169
+ tags=["Workflows"],
170
+ )
171
+
172
+
173
+ if __name__ == "__main__":
174
+ uvicorn.run(
175
+ "julee.api.app:app",
176
+ host="0.0.0.0",
177
+ port=8000,
178
+ reload=True,
179
+ log_level="info",
180
+ )
@@ -0,0 +1,257 @@
1
+ """
2
+ Dependency injection for julee FastAPI endpoints.
3
+
4
+ This module provides dependency injection for the julee API endpoints,
5
+ following the same patterns established in the sample project. It manages
6
+ singleton lifecycle for expensive resources and provides clean separation
7
+ between infrastructure concerns and business logic.
8
+
9
+ The dependencies focus on real Minio implementations for production use,
10
+ with test overrides available through FastAPI's dependency override system.
11
+ """
12
+
13
+ import os
14
+ import logging
15
+ from typing import Any, Dict, TYPE_CHECKING
16
+
17
+ if TYPE_CHECKING:
18
+ from julee.api.services.system_initialization import (
19
+ SystemInitializationService,
20
+ )
21
+
22
+ from fastapi import Depends
23
+ from temporalio.client import Client
24
+ from temporalio.contrib.pydantic import pydantic_data_converter
25
+
26
+ from julee.domain.repositories.knowledge_service_query import (
27
+ KnowledgeServiceQueryRepository,
28
+ )
29
+ from julee.domain.repositories.knowledge_service_config import (
30
+ KnowledgeServiceConfigRepository,
31
+ )
32
+ from julee.domain.repositories.assembly_specification import (
33
+ AssemblySpecificationRepository,
34
+ )
35
+ from julee.domain.repositories.document import (
36
+ DocumentRepository,
37
+ )
38
+ from julee.repositories.minio.knowledge_service_query import (
39
+ MinioKnowledgeServiceQueryRepository,
40
+ )
41
+ from julee.repositories.minio.knowledge_service_config import (
42
+ MinioKnowledgeServiceConfigRepository,
43
+ )
44
+ from julee.repositories.minio.assembly_specification import (
45
+ MinioAssemblySpecificationRepository,
46
+ )
47
+ from julee.repositories.minio.document import (
48
+ MinioDocumentRepository,
49
+ )
50
+ from julee.repositories.minio.client import MinioClient
51
+ from minio import Minio
52
+
53
+ logger = logging.getLogger(__name__)
54
+
55
+
56
+ class DependencyContainer:
57
+ """
58
+ Dependency injection container with singleton lifecycle management.
59
+ Always creates real clients; mocks are provided by test overrides.
60
+ """
61
+
62
+ def __init__(self) -> None:
63
+ self._instances: Dict[str, Any] = {}
64
+
65
+ async def get_or_create(self, key: str, factory: Any) -> Any:
66
+ """Get or create a singleton instance."""
67
+ if key not in self._instances:
68
+ self._instances[key] = await factory()
69
+ return self._instances[key]
70
+
71
+ async def get_temporal_client(self) -> Client:
72
+ """Get or create Temporal client."""
73
+ client = await self.get_or_create(
74
+ "temporal_client", self._create_temporal_client
75
+ )
76
+ return client # type: ignore[no-any-return]
77
+
78
+ async def _create_temporal_client(self) -> Client:
79
+ """Create Temporal client with proper configuration."""
80
+ temporal_endpoint = os.environ.get("TEMPORAL_ENDPOINT", "temporal:7233")
81
+ logger.debug(
82
+ "Creating Temporal client",
83
+ extra={"endpoint": temporal_endpoint, "namespace": "default"},
84
+ )
85
+
86
+ client = await Client.connect(
87
+ temporal_endpoint,
88
+ namespace="default",
89
+ data_converter=pydantic_data_converter,
90
+ )
91
+
92
+ logger.debug(
93
+ "Temporal client created",
94
+ extra={
95
+ "endpoint": temporal_endpoint,
96
+ "data_converter_type": type(client.data_converter).__name__,
97
+ },
98
+ )
99
+ return client
100
+
101
+ async def get_minio_client(self) -> MinioClient:
102
+ """Get or create Minio client."""
103
+ client = await self.get_or_create("minio_client", self._create_minio_client)
104
+ return client # type: ignore[no-any-return]
105
+
106
+ async def _create_minio_client(self) -> MinioClient:
107
+ """Create Minio client with proper configuration."""
108
+ endpoint = os.environ.get("MINIO_ENDPOINT", "localhost:9000")
109
+ access_key = os.environ.get("MINIO_ACCESS_KEY", "minioadmin")
110
+ secret_key = os.environ.get("MINIO_SECRET_KEY", "minioadmin")
111
+ secure = os.environ.get("MINIO_SECURE", "false").lower() == "true"
112
+
113
+ logger.debug(
114
+ "Creating Minio client",
115
+ extra={
116
+ "endpoint": endpoint,
117
+ "secure": secure,
118
+ "access_key": access_key[:4] + "***", # Log partial key only
119
+ },
120
+ )
121
+
122
+ # Create the actual minio client which implements MinioClient protocol
123
+ client = Minio(
124
+ endpoint=endpoint,
125
+ access_key=access_key,
126
+ secret_key=secret_key,
127
+ secure=secure,
128
+ )
129
+
130
+ logger.debug("Minio client created", extra={"endpoint": endpoint})
131
+ return client # type: ignore[return-value]
132
+
133
+
134
+ # Global container instance
135
+ _container = DependencyContainer()
136
+
137
+
138
+ async def get_temporal_client() -> Client:
139
+ """FastAPI dependency for Temporal client."""
140
+ return await _container.get_temporal_client()
141
+
142
+
143
+ async def get_minio_client() -> MinioClient:
144
+ """FastAPI dependency for Minio client."""
145
+ return await _container.get_minio_client()
146
+
147
+
148
+ async def get_knowledge_service_query_repository(
149
+ minio_client: MinioClient = Depends(get_minio_client),
150
+ ) -> KnowledgeServiceQueryRepository:
151
+ """FastAPI dependency for KnowledgeServiceQueryRepository."""
152
+ return MinioKnowledgeServiceQueryRepository(client=minio_client)
153
+
154
+
155
+ async def get_knowledge_service_config_repository(
156
+ minio_client: MinioClient = Depends(get_minio_client),
157
+ ) -> KnowledgeServiceConfigRepository:
158
+ """FastAPI dependency for KnowledgeServiceConfigRepository."""
159
+ return MinioKnowledgeServiceConfigRepository(client=minio_client)
160
+
161
+
162
+ async def get_assembly_specification_repository(
163
+ minio_client: MinioClient = Depends(get_minio_client),
164
+ ) -> AssemblySpecificationRepository:
165
+ """FastAPI dependency for AssemblySpecificationRepository."""
166
+ return MinioAssemblySpecificationRepository(client=minio_client)
167
+
168
+
169
+ async def get_document_repository(
170
+ minio_client: MinioClient = Depends(get_minio_client),
171
+ ) -> DocumentRepository:
172
+ """FastAPI dependency for DocumentRepository."""
173
+ return MinioDocumentRepository(client=minio_client)
174
+
175
+
176
+ class StartupDependenciesProvider:
177
+ """
178
+ Provider for dependencies needed during application startup.
179
+
180
+ This class provides clean access to repositories and services needed
181
+ during the lifespan startup phase, without exposing internal container
182
+ details or requiring FastAPI's dependency injection system.
183
+ """
184
+
185
+ def __init__(self, container: DependencyContainer):
186
+ """Initialize with dependency container."""
187
+ self.container = container
188
+ self.logger = logging.getLogger("StartupDependenciesProvider")
189
+
190
+ async def get_document_repository(self) -> DocumentRepository:
191
+ """Get document repository for startup dependencies."""
192
+ minio_client = await self.container.get_minio_client()
193
+ from julee.repositories.minio.document import (
194
+ MinioDocumentRepository,
195
+ )
196
+
197
+ return MinioDocumentRepository(client=minio_client)
198
+
199
+ async def get_knowledge_service_config_repository(
200
+ self,
201
+ ) -> KnowledgeServiceConfigRepository:
202
+ """Get knowledge service config repository for startup."""
203
+ minio_client = await self.container.get_minio_client()
204
+ return MinioKnowledgeServiceConfigRepository(client=minio_client)
205
+
206
+ async def get_knowledge_service_query_repository(
207
+ self,
208
+ ) -> KnowledgeServiceQueryRepository:
209
+ """Get knowledge service query repository for startup dependencies."""
210
+ minio_client = await self.container.get_minio_client()
211
+ return MinioKnowledgeServiceQueryRepository(client=minio_client)
212
+
213
+ async def get_assembly_specification_repository(
214
+ self,
215
+ ) -> AssemblySpecificationRepository:
216
+ """Get assembly specification repository for startup dependencies."""
217
+ minio_client = await self.container.get_minio_client()
218
+ return MinioAssemblySpecificationRepository(client=minio_client)
219
+
220
+ async def get_system_initialization_service(
221
+ self,
222
+ ) -> "SystemInitializationService":
223
+ """Get fully configured system initialization service."""
224
+ from julee.api.services.system_initialization import (
225
+ SystemInitializationService,
226
+ )
227
+ from julee.domain.use_cases.initialize_system_data import (
228
+ InitializeSystemDataUseCase,
229
+ )
230
+
231
+ self.logger.debug("Creating system initialization service")
232
+
233
+ # Create repositories and use case
234
+ config_repo = await self.get_knowledge_service_config_repository()
235
+ document_repo = await self.get_document_repository()
236
+ query_repo = await self.get_knowledge_service_query_repository()
237
+ assembly_spec_repo = await self.get_assembly_specification_repository()
238
+ use_case = InitializeSystemDataUseCase(
239
+ config_repo, document_repo, query_repo, assembly_spec_repo
240
+ )
241
+
242
+ # Create and return service
243
+ return SystemInitializationService(use_case)
244
+
245
+
246
+ # Global startup dependencies provider
247
+ _startup_provider = StartupDependenciesProvider(_container)
248
+
249
+
250
+ async def get_startup_dependencies() -> StartupDependenciesProvider:
251
+ """Get startup dependencies provider for lifespan contexts."""
252
+ return _startup_provider
253
+
254
+
255
+ # Note: Use cases and more complex dependencies can be added here as needed
256
+ # following the same pattern. For simple CRUD operations like listing
257
+ # queries, we can use the repository directly in the endpoint.
julee/api/requests.py ADDED
@@ -0,0 +1,175 @@
1
+ """
2
+ Pydantic models for API requests.
3
+ These define the contract between the API and external clients.
4
+
5
+ Following clean architecture principles, request models delegate validation
6
+ to domain model class methods and reuse field descriptions to avoid
7
+ duplication while maintaining single source of truth in the domain layer.
8
+ """
9
+
10
+ from typing import Dict, Any, Optional
11
+ from pydantic import BaseModel, Field, field_validator, ValidationInfo
12
+ from datetime import datetime, timezone
13
+
14
+ from julee.domain.models import (
15
+ AssemblySpecification,
16
+ AssemblySpecificationStatus,
17
+ KnowledgeServiceQuery,
18
+ )
19
+
20
+
21
+ class CreateAssemblySpecificationRequest(BaseModel):
22
+ """Request model for creating an assembly specification.
23
+
24
+ This model defines what clients need to provide when creating a new
25
+ assembly specification. Validation logic is delegated to the domain
26
+ model to ensure consistency and avoid duplication.
27
+
28
+ Fields excluded from client control:
29
+ - assembly_specification_id: Always generated by the server
30
+ - status: Always set to DRAFT initially by the server
31
+ - created_at/updated_at: System-managed timestamps
32
+ """
33
+
34
+ # Field definitions with descriptions reused from domain model
35
+ name: str = Field(
36
+ description=AssemblySpecification.model_fields["name"].description
37
+ )
38
+ applicability: str = Field(
39
+ description=AssemblySpecification.model_fields["applicability"].description
40
+ )
41
+ jsonschema: Dict[str, Any] = Field(
42
+ description=AssemblySpecification.model_fields["jsonschema"].description
43
+ )
44
+ knowledge_service_queries: Dict[str, str] = Field(
45
+ default_factory=dict,
46
+ description=AssemblySpecification.model_fields[
47
+ "knowledge_service_queries"
48
+ ].description,
49
+ )
50
+ version: str = Field(
51
+ default=AssemblySpecification.model_fields["version"].default,
52
+ description=AssemblySpecification.model_fields["version"].description,
53
+ )
54
+
55
+ # Delegate validation to domain model class methods
56
+ @field_validator("name")
57
+ @classmethod
58
+ def validate_name(cls, v: str) -> str:
59
+ return AssemblySpecification.name_must_not_be_empty(v)
60
+
61
+ @field_validator("applicability")
62
+ @classmethod
63
+ def validate_applicability(cls, v: str) -> str:
64
+ return AssemblySpecification.applicability_must_not_be_empty(v)
65
+
66
+ @field_validator("jsonschema")
67
+ @classmethod
68
+ def validate_jsonschema(cls, v: Dict[str, Any]) -> Dict[str, Any]:
69
+ return AssemblySpecification.jsonschema_must_be_valid(v)
70
+
71
+ @field_validator("knowledge_service_queries")
72
+ @classmethod
73
+ def validate_knowledge_service_queries(
74
+ cls, v: Dict[str, str], info: ValidationInfo
75
+ ) -> Dict[str, str]:
76
+ return AssemblySpecification.knowledge_service_queries_must_be_valid(v, info)
77
+
78
+ @field_validator("version")
79
+ @classmethod
80
+ def validate_version(cls, v: str) -> str:
81
+ return AssemblySpecification.version_must_not_be_empty(v)
82
+
83
+ def to_domain_model(self, assembly_specification_id: str) -> AssemblySpecification:
84
+ """Convert this request to a complete AssemblySpecification object.
85
+
86
+ Args:
87
+ assembly_specification_id: The ID to assign to the new spec
88
+
89
+ Returns:
90
+ AssemblySpecification: Complete domain object with system fields
91
+ """
92
+ now = datetime.now(timezone.utc)
93
+ return AssemblySpecification(
94
+ assembly_specification_id=assembly_specification_id,
95
+ name=self.name,
96
+ applicability=self.applicability,
97
+ jsonschema=self.jsonschema,
98
+ knowledge_service_queries=self.knowledge_service_queries,
99
+ version=self.version,
100
+ status=AssemblySpecificationStatus.DRAFT,
101
+ created_at=now,
102
+ updated_at=now,
103
+ )
104
+
105
+
106
+ class CreateKnowledgeServiceQueryRequest(BaseModel):
107
+ """Request model for creating a knowledge service query.
108
+
109
+ This model defines what clients need to provide when creating a new
110
+ knowledge service query. Validation logic is delegated to the domain
111
+ model and descriptions are reused to avoid duplication while maintaining
112
+ single source of truth in the domain layer.
113
+
114
+ Fields excluded from client control:
115
+ - query_id: Always generated by the server
116
+ - created_at/updated_at: System-managed timestamps
117
+ """
118
+
119
+ # Field definitions with descriptions reused from domain model
120
+ name: str = Field(
121
+ description=KnowledgeServiceQuery.model_fields["name"].description
122
+ )
123
+ knowledge_service_id: str = Field(
124
+ description=KnowledgeServiceQuery.model_fields[
125
+ "knowledge_service_id"
126
+ ].description
127
+ )
128
+ prompt: str = Field(
129
+ description=KnowledgeServiceQuery.model_fields["prompt"].description
130
+ )
131
+ query_metadata: Dict[str, Any] = Field(
132
+ default_factory=dict,
133
+ description=KnowledgeServiceQuery.model_fields["query_metadata"].description,
134
+ )
135
+ assistant_prompt: Optional[str] = Field(
136
+ default=KnowledgeServiceQuery.model_fields["assistant_prompt"].default,
137
+ description=KnowledgeServiceQuery.model_fields["assistant_prompt"].description,
138
+ )
139
+
140
+ # Delegate validation to domain model class methods
141
+ @field_validator("name")
142
+ @classmethod
143
+ def validate_name(cls, v: str) -> str:
144
+ return KnowledgeServiceQuery.name_must_not_be_empty(v)
145
+
146
+ @field_validator("knowledge_service_id")
147
+ @classmethod
148
+ def validate_knowledge_service_id(cls, v: str) -> str:
149
+ return KnowledgeServiceQuery.knowledge_service_id_must_not_be_empty(v)
150
+
151
+ @field_validator("prompt")
152
+ @classmethod
153
+ def validate_prompt(cls, v: str) -> str:
154
+ return KnowledgeServiceQuery.prompt_must_not_be_empty(v)
155
+
156
+ def to_domain_model(self, query_id: str) -> KnowledgeServiceQuery:
157
+ """Convert this request to a complete KnowledgeServiceQuery object.
158
+
159
+ Args:
160
+ query_id: The ID to assign to the new query
161
+
162
+ Returns:
163
+ KnowledgeServiceQuery: Complete domain object with system fields
164
+ """
165
+ now = datetime.now(timezone.utc)
166
+ return KnowledgeServiceQuery(
167
+ query_id=query_id,
168
+ name=self.name,
169
+ knowledge_service_id=self.knowledge_service_id,
170
+ prompt=self.prompt,
171
+ query_metadata=self.query_metadata,
172
+ assistant_prompt=self.assistant_prompt,
173
+ created_at=now,
174
+ updated_at=now,
175
+ )
julee/api/responses.py ADDED
@@ -0,0 +1,43 @@
1
+ """
2
+ Pydantic models for API responses.
3
+ These define the contract between the API and external clients.
4
+
5
+ Following clean architecture principles, most endpoints return domain models
6
+ directly rather than creating wrapper response models. This file contains
7
+ only response models that are specific to API concerns and not represented
8
+ by existing domain models.
9
+ """
10
+
11
+ from pydantic import BaseModel
12
+ from enum import Enum
13
+
14
+
15
+ class ServiceStatus(str, Enum):
16
+ """Service status enumeration."""
17
+
18
+ UP = "up"
19
+ DOWN = "down"
20
+
21
+
22
+ class SystemStatus(str, Enum):
23
+ """Overall system status enumeration."""
24
+
25
+ HEALTHY = "healthy"
26
+ DEGRADED = "degraded"
27
+ UNHEALTHY = "unhealthy"
28
+
29
+
30
+ class ServiceHealthStatus(BaseModel):
31
+ """Health status for individual services."""
32
+
33
+ api: ServiceStatus
34
+ temporal: ServiceStatus
35
+ storage: ServiceStatus
36
+
37
+
38
+ class HealthCheckResponse(BaseModel):
39
+ """Response for health check endpoint."""
40
+
41
+ status: SystemStatus
42
+ timestamp: str
43
+ services: ServiceHealthStatus