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.
- julee/__init__.py +3 -0
- julee/api/__init__.py +20 -0
- julee/api/app.py +180 -0
- julee/api/dependencies.py +257 -0
- julee/api/requests.py +175 -0
- julee/api/responses.py +43 -0
- julee/api/routers/__init__.py +43 -0
- julee/api/routers/assembly_specifications.py +212 -0
- julee/api/routers/documents.py +182 -0
- julee/api/routers/knowledge_service_configs.py +79 -0
- julee/api/routers/knowledge_service_queries.py +293 -0
- julee/api/routers/system.py +137 -0
- julee/api/routers/workflows.py +234 -0
- julee/api/services/__init__.py +20 -0
- julee/api/services/system_initialization.py +214 -0
- julee/api/tests/__init__.py +14 -0
- julee/api/tests/routers/__init__.py +17 -0
- julee/api/tests/routers/test_assembly_specifications.py +749 -0
- julee/api/tests/routers/test_documents.py +301 -0
- julee/api/tests/routers/test_knowledge_service_configs.py +234 -0
- julee/api/tests/routers/test_knowledge_service_queries.py +738 -0
- julee/api/tests/routers/test_system.py +179 -0
- julee/api/tests/routers/test_workflows.py +393 -0
- julee/api/tests/test_app.py +285 -0
- julee/api/tests/test_dependencies.py +245 -0
- julee/api/tests/test_requests.py +250 -0
- julee/domain/__init__.py +22 -0
- julee/domain/models/__init__.py +49 -0
- julee/domain/models/assembly/__init__.py +17 -0
- julee/domain/models/assembly/assembly.py +103 -0
- julee/domain/models/assembly/tests/__init__.py +0 -0
- julee/domain/models/assembly/tests/factories.py +37 -0
- julee/domain/models/assembly/tests/test_assembly.py +430 -0
- julee/domain/models/assembly_specification/__init__.py +24 -0
- julee/domain/models/assembly_specification/assembly_specification.py +172 -0
- julee/domain/models/assembly_specification/knowledge_service_query.py +123 -0
- julee/domain/models/assembly_specification/tests/__init__.py +0 -0
- julee/domain/models/assembly_specification/tests/factories.py +78 -0
- julee/domain/models/assembly_specification/tests/test_assembly_specification.py +490 -0
- julee/domain/models/assembly_specification/tests/test_knowledge_service_query.py +310 -0
- julee/domain/models/custom_fields/__init__.py +0 -0
- julee/domain/models/custom_fields/content_stream.py +68 -0
- julee/domain/models/custom_fields/tests/__init__.py +0 -0
- julee/domain/models/custom_fields/tests/test_custom_fields.py +53 -0
- julee/domain/models/document/__init__.py +17 -0
- julee/domain/models/document/document.py +150 -0
- julee/domain/models/document/tests/__init__.py +0 -0
- julee/domain/models/document/tests/factories.py +76 -0
- julee/domain/models/document/tests/test_document.py +297 -0
- julee/domain/models/knowledge_service_config/__init__.py +17 -0
- julee/domain/models/knowledge_service_config/knowledge_service_config.py +86 -0
- julee/domain/models/policy/__init__.py +15 -0
- julee/domain/models/policy/document_policy_validation.py +220 -0
- julee/domain/models/policy/policy.py +203 -0
- julee/domain/models/policy/tests/__init__.py +0 -0
- julee/domain/models/policy/tests/factories.py +47 -0
- julee/domain/models/policy/tests/test_document_policy_validation.py +420 -0
- julee/domain/models/policy/tests/test_policy.py +546 -0
- julee/domain/repositories/__init__.py +27 -0
- julee/domain/repositories/assembly.py +45 -0
- julee/domain/repositories/assembly_specification.py +52 -0
- julee/domain/repositories/base.py +146 -0
- julee/domain/repositories/document.py +49 -0
- julee/domain/repositories/document_policy_validation.py +52 -0
- julee/domain/repositories/knowledge_service_config.py +54 -0
- julee/domain/repositories/knowledge_service_query.py +44 -0
- julee/domain/repositories/policy.py +49 -0
- julee/domain/use_cases/__init__.py +17 -0
- julee/domain/use_cases/decorators.py +107 -0
- julee/domain/use_cases/extract_assemble_data.py +649 -0
- julee/domain/use_cases/initialize_system_data.py +842 -0
- julee/domain/use_cases/tests/__init__.py +7 -0
- julee/domain/use_cases/tests/test_extract_assemble_data.py +548 -0
- julee/domain/use_cases/tests/test_initialize_system_data.py +455 -0
- julee/domain/use_cases/tests/test_validate_document.py +1228 -0
- julee/domain/use_cases/validate_document.py +736 -0
- julee/fixtures/assembly_specifications.yaml +70 -0
- julee/fixtures/documents.yaml +178 -0
- julee/fixtures/knowledge_service_configs.yaml +37 -0
- julee/fixtures/knowledge_service_queries.yaml +27 -0
- julee/repositories/__init__.py +17 -0
- julee/repositories/memory/__init__.py +31 -0
- julee/repositories/memory/assembly.py +84 -0
- julee/repositories/memory/assembly_specification.py +125 -0
- julee/repositories/memory/base.py +227 -0
- julee/repositories/memory/document.py +149 -0
- julee/repositories/memory/document_policy_validation.py +104 -0
- julee/repositories/memory/knowledge_service_config.py +123 -0
- julee/repositories/memory/knowledge_service_query.py +120 -0
- julee/repositories/memory/policy.py +87 -0
- julee/repositories/memory/tests/__init__.py +0 -0
- julee/repositories/memory/tests/test_document.py +212 -0
- julee/repositories/memory/tests/test_document_policy_validation.py +161 -0
- julee/repositories/memory/tests/test_policy.py +443 -0
- julee/repositories/minio/__init__.py +31 -0
- julee/repositories/minio/assembly.py +103 -0
- julee/repositories/minio/assembly_specification.py +170 -0
- julee/repositories/minio/client.py +570 -0
- julee/repositories/minio/document.py +530 -0
- julee/repositories/minio/document_policy_validation.py +120 -0
- julee/repositories/minio/knowledge_service_config.py +187 -0
- julee/repositories/minio/knowledge_service_query.py +211 -0
- julee/repositories/minio/policy.py +106 -0
- julee/repositories/minio/tests/__init__.py +0 -0
- julee/repositories/minio/tests/fake_client.py +213 -0
- julee/repositories/minio/tests/test_assembly.py +374 -0
- julee/repositories/minio/tests/test_assembly_specification.py +391 -0
- julee/repositories/minio/tests/test_client_protocol.py +57 -0
- julee/repositories/minio/tests/test_document.py +591 -0
- julee/repositories/minio/tests/test_document_policy_validation.py +192 -0
- julee/repositories/minio/tests/test_knowledge_service_config.py +374 -0
- julee/repositories/minio/tests/test_knowledge_service_query.py +438 -0
- julee/repositories/minio/tests/test_policy.py +559 -0
- julee/repositories/temporal/__init__.py +38 -0
- julee/repositories/temporal/activities.py +114 -0
- julee/repositories/temporal/activity_names.py +34 -0
- julee/repositories/temporal/proxies.py +159 -0
- julee/services/__init__.py +18 -0
- julee/services/knowledge_service/__init__.py +48 -0
- julee/services/knowledge_service/anthropic/__init__.py +12 -0
- julee/services/knowledge_service/anthropic/knowledge_service.py +331 -0
- julee/services/knowledge_service/anthropic/tests/test_knowledge_service.py +318 -0
- julee/services/knowledge_service/factory.py +138 -0
- julee/services/knowledge_service/knowledge_service.py +160 -0
- julee/services/knowledge_service/memory/__init__.py +13 -0
- julee/services/knowledge_service/memory/knowledge_service.py +278 -0
- julee/services/knowledge_service/memory/test_knowledge_service.py +345 -0
- julee/services/knowledge_service/test_factory.py +112 -0
- julee/services/temporal/__init__.py +38 -0
- julee/services/temporal/activities.py +86 -0
- julee/services/temporal/activity_names.py +22 -0
- julee/services/temporal/proxies.py +41 -0
- julee/util/__init__.py +0 -0
- julee/util/domain.py +119 -0
- julee/util/repos/__init__.py +0 -0
- julee/util/repos/minio/__init__.py +0 -0
- julee/util/repos/minio/file_storage.py +213 -0
- julee/util/repos/temporal/__init__.py +11 -0
- julee/util/repos/temporal/client_proxies/file_storage.py +68 -0
- julee/util/repos/temporal/data_converter.py +123 -0
- julee/util/repos/temporal/minio_file_storage.py +12 -0
- julee/util/repos/temporal/proxies/__init__.py +0 -0
- julee/util/repos/temporal/proxies/file_storage.py +58 -0
- julee/util/repositories.py +55 -0
- julee/util/temporal/__init__.py +22 -0
- julee/util/temporal/activities.py +123 -0
- julee/util/temporal/decorators.py +473 -0
- julee/util/tests/__init__.py +1 -0
- julee/util/tests/test_decorators.py +770 -0
- julee/util/validation/__init__.py +29 -0
- julee/util/validation/repository.py +100 -0
- julee/util/validation/type_guards.py +369 -0
- julee/worker.py +211 -0
- julee/workflows/__init__.py +26 -0
- julee/workflows/extract_assemble.py +215 -0
- julee/workflows/validate_document.py +228 -0
- julee-0.1.0.dist-info/METADATA +195 -0
- julee-0.1.0.dist-info/RECORD +161 -0
- julee-0.1.0.dist-info/WHEEL +5 -0
- julee-0.1.0.dist-info/licenses/LICENSE +674 -0
- julee-0.1.0.dist-info/top_level.txt +1 -0
julee/__init__.py
ADDED
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
|