julee 0.1.2__py3-none-any.whl → 0.1.4__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/api/app.py +9 -8
- julee/api/dependencies.py +15 -15
- julee/api/requests.py +10 -9
- julee/api/responses.py +2 -1
- julee/api/routers/__init__.py +5 -5
- julee/api/routers/assembly_specifications.py +5 -4
- julee/api/routers/documents.py +1 -1
- julee/api/routers/knowledge_service_configs.py +4 -3
- julee/api/routers/knowledge_service_queries.py +7 -6
- julee/api/routers/system.py +4 -3
- julee/api/routers/workflows.py +4 -5
- julee/api/services/system_initialization.py +6 -6
- julee/api/tests/routers/test_assembly_specifications.py +4 -3
- julee/api/tests/routers/test_documents.py +11 -10
- julee/api/tests/routers/test_knowledge_service_configs.py +7 -6
- julee/api/tests/routers/test_knowledge_service_queries.py +4 -3
- julee/api/tests/routers/test_system.py +5 -4
- julee/api/tests/routers/test_workflows.py +5 -4
- julee/api/tests/test_app.py +5 -4
- julee/api/tests/test_dependencies.py +3 -2
- julee/api/tests/test_requests.py +2 -1
- julee/contrib/__init__.py +15 -0
- julee/contrib/polling/__init__.py +47 -0
- julee/contrib/polling/domain/__init__.py +17 -0
- julee/contrib/polling/domain/models/__init__.py +13 -0
- julee/contrib/polling/domain/models/polling_config.py +39 -0
- julee/contrib/polling/domain/services/__init__.py +11 -0
- julee/contrib/polling/domain/services/poller.py +39 -0
- julee/contrib/polling/infrastructure/__init__.py +15 -0
- julee/contrib/polling/infrastructure/services/__init__.py +12 -0
- julee/contrib/polling/infrastructure/services/polling/__init__.py +12 -0
- julee/contrib/polling/infrastructure/services/polling/http/__init__.py +12 -0
- julee/contrib/polling/infrastructure/services/polling/http/http_poller_service.py +80 -0
- julee/contrib/polling/infrastructure/temporal/__init__.py +20 -0
- julee/contrib/polling/infrastructure/temporal/activities.py +42 -0
- julee/contrib/polling/infrastructure/temporal/activity_names.py +20 -0
- julee/contrib/polling/infrastructure/temporal/proxies.py +45 -0
- julee/contrib/polling/tests/__init__.py +6 -0
- julee/contrib/polling/tests/unit/__init__.py +6 -0
- julee/contrib/polling/tests/unit/infrastructure/__init__.py +7 -0
- julee/contrib/polling/tests/unit/infrastructure/services/__init__.py +6 -0
- julee/contrib/polling/tests/unit/infrastructure/services/polling/__init__.py +6 -0
- julee/contrib/polling/tests/unit/infrastructure/services/polling/http/__init__.py +7 -0
- julee/contrib/polling/tests/unit/infrastructure/services/polling/http/test_http_poller_service.py +163 -0
- julee/docs/__init__.py +5 -0
- julee/docs/sphinx_hcd/__init__.py +76 -0
- julee/docs/sphinx_hcd/accelerators.py +1175 -0
- julee/docs/sphinx_hcd/apps.py +518 -0
- julee/docs/sphinx_hcd/config.py +148 -0
- julee/docs/sphinx_hcd/epics.py +453 -0
- julee/docs/sphinx_hcd/integrations.py +310 -0
- julee/docs/sphinx_hcd/journeys.py +797 -0
- julee/docs/sphinx_hcd/personas.py +457 -0
- julee/docs/sphinx_hcd/stories.py +960 -0
- julee/docs/sphinx_hcd/utils.py +185 -0
- julee/domain/models/__init__.py +5 -6
- julee/domain/models/assembly/assembly.py +7 -7
- julee/domain/models/assembly/tests/factories.py +2 -1
- julee/domain/models/assembly/tests/test_assembly.py +16 -13
- julee/domain/models/assembly_specification/assembly_specification.py +11 -10
- julee/domain/models/assembly_specification/knowledge_service_query.py +7 -6
- julee/domain/models/assembly_specification/tests/factories.py +2 -1
- julee/domain/models/assembly_specification/tests/test_assembly_specification.py +9 -6
- julee/domain/models/assembly_specification/tests/test_knowledge_service_query.py +3 -1
- julee/domain/models/custom_fields/content_stream.py +3 -2
- julee/domain/models/custom_fields/tests/test_custom_fields.py +2 -1
- julee/domain/models/document/document.py +23 -30
- julee/domain/models/document/tests/factories.py +3 -2
- julee/domain/models/document/tests/test_document.py +20 -37
- julee/domain/models/knowledge_service_config/knowledge_service_config.py +4 -4
- julee/domain/models/policy/__init__.py +4 -4
- julee/domain/models/policy/document_policy_validation.py +17 -17
- julee/domain/models/policy/policy.py +10 -10
- julee/domain/models/policy/tests/factories.py +2 -1
- julee/domain/models/policy/tests/test_document_policy_validation.py +3 -1
- julee/domain/models/policy/tests/test_policy.py +2 -1
- julee/domain/repositories/__init__.py +3 -3
- julee/domain/repositories/assembly.py +3 -1
- julee/domain/repositories/assembly_specification.py +2 -0
- julee/domain/repositories/base.py +5 -4
- julee/domain/repositories/document.py +3 -1
- julee/domain/repositories/document_policy_validation.py +3 -1
- julee/domain/repositories/knowledge_service_config.py +2 -0
- julee/domain/repositories/knowledge_service_query.py +1 -0
- julee/domain/repositories/policy.py +3 -1
- julee/domain/use_cases/decorators.py +3 -2
- julee/domain/use_cases/extract_assemble_data.py +14 -13
- julee/domain/use_cases/initialize_system_data.py +88 -34
- julee/domain/use_cases/tests/test_extract_assemble_data.py +10 -10
- julee/domain/use_cases/tests/test_initialize_system_data.py +2 -2
- julee/domain/use_cases/tests/test_validate_document.py +11 -11
- julee/domain/use_cases/validate_document.py +14 -14
- julee/fixtures/documents.yaml +4 -43
- julee/fixtures/knowledge_service_queries.yaml +9 -0
- julee/maintenance/__init__.py +1 -0
- julee/maintenance/release.py +243 -0
- julee/repositories/memory/assembly.py +6 -5
- julee/repositories/memory/assembly_specification.py +8 -9
- julee/repositories/memory/base.py +12 -11
- julee/repositories/memory/document.py +27 -20
- julee/repositories/memory/document_policy_validation.py +7 -6
- julee/repositories/memory/knowledge_service_config.py +8 -7
- julee/repositories/memory/knowledge_service_query.py +8 -7
- julee/repositories/memory/policy.py +6 -5
- julee/repositories/memory/tests/test_document.py +24 -22
- julee/repositories/memory/tests/test_document_policy_validation.py +2 -1
- julee/repositories/memory/tests/test_policy.py +2 -1
- julee/repositories/minio/assembly.py +4 -4
- julee/repositories/minio/assembly_specification.py +6 -8
- julee/repositories/minio/client.py +22 -25
- julee/repositories/minio/document.py +36 -33
- julee/repositories/minio/document_policy_validation.py +5 -5
- julee/repositories/minio/knowledge_service_config.py +6 -6
- julee/repositories/minio/knowledge_service_query.py +6 -9
- julee/repositories/minio/policy.py +4 -4
- julee/repositories/minio/tests/fake_client.py +11 -9
- julee/repositories/minio/tests/test_assembly.py +3 -1
- julee/repositories/minio/tests/test_assembly_specification.py +2 -1
- julee/repositories/minio/tests/test_client_protocol.py +5 -5
- julee/repositories/minio/tests/test_document.py +23 -22
- julee/repositories/minio/tests/test_document_policy_validation.py +3 -1
- julee/repositories/minio/tests/test_knowledge_service_config.py +4 -2
- julee/repositories/minio/tests/test_knowledge_service_query.py +3 -2
- julee/repositories/minio/tests/test_policy.py +3 -1
- julee/repositories/temporal/activities.py +5 -5
- julee/repositories/temporal/proxies.py +5 -5
- julee/services/knowledge_service/__init__.py +1 -2
- julee/services/knowledge_service/anthropic/knowledge_service.py +8 -7
- julee/services/knowledge_service/anthropic/tests/test_knowledge_service.py +11 -10
- julee/services/knowledge_service/factory.py +8 -8
- julee/services/knowledge_service/knowledge_service.py +12 -14
- julee/services/knowledge_service/memory/knowledge_service.py +13 -12
- julee/services/knowledge_service/memory/test_knowledge_service.py +10 -7
- julee/services/knowledge_service/test_factory.py +11 -10
- julee/services/temporal/activities.py +10 -10
- julee/services/temporal/proxies.py +2 -2
- julee/util/domain.py +6 -6
- julee/util/repos/minio/file_storage.py +8 -9
- julee/util/repos/temporal/client_proxies/file_storage.py +3 -4
- julee/util/repos/temporal/data_converter.py +6 -6
- julee/util/repos/temporal/minio_file_storage.py +1 -1
- julee/util/repos/temporal/proxies/file_storage.py +2 -3
- julee/util/repositories.py +4 -3
- julee/util/temporal/decorators.py +20 -18
- julee/util/tests/test_decorators.py +13 -15
- julee/util/validation/repository.py +3 -3
- julee/util/validation/type_guards.py +12 -11
- julee/worker.py +9 -8
- julee/workflows/__init__.py +2 -2
- julee/workflows/extract_assemble.py +2 -1
- julee/workflows/validate_document.py +3 -2
- {julee-0.1.2.dist-info → julee-0.1.4.dist-info}/METADATA +3 -3
- julee-0.1.4.dist-info/RECORD +196 -0
- julee/fixtures/assembly_specifications.yaml +0 -70
- julee-0.1.2.dist-info/RECORD +0 -161
- {julee-0.1.2.dist-info → julee-0.1.4.dist-info}/WHEEL +0 -0
- {julee-0.1.2.dist-info → julee-0.1.4.dist-info}/licenses/LICENSE +0 -0
- {julee-0.1.2.dist-info → julee-0.1.4.dist-info}/top_level.txt +0 -0
|
@@ -8,11 +8,13 @@ All domain models use Pydantic BaseModel for validation, serialization,
|
|
|
8
8
|
and type safety, following the patterns established in the sample project.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
from
|
|
12
|
-
from pydantic import ValidationInfo
|
|
13
|
-
from typing import Callable, Optional, List, Dict, Any
|
|
11
|
+
from collections.abc import Callable
|
|
14
12
|
from datetime import datetime, timezone
|
|
15
13
|
from enum import Enum
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from pydantic import BaseModel, Field, ValidationInfo, field_validator, model_validator
|
|
17
|
+
|
|
16
18
|
from julee.domain.models.custom_fields.content_stream import (
|
|
17
19
|
ContentStream,
|
|
18
20
|
)
|
|
@@ -75,27 +77,25 @@ class Document(BaseModel):
|
|
|
75
77
|
|
|
76
78
|
# Document processing state
|
|
77
79
|
status: DocumentStatus = DocumentStatus.CAPTURED
|
|
78
|
-
knowledge_service_id:
|
|
79
|
-
assembly_types:
|
|
80
|
+
knowledge_service_id: str | None = None
|
|
81
|
+
assembly_types: list[str] = Field(default_factory=list)
|
|
80
82
|
|
|
81
83
|
# Timestamps
|
|
82
|
-
created_at:
|
|
84
|
+
created_at: datetime | None = Field(
|
|
83
85
|
default_factory=lambda: datetime.now(timezone.utc)
|
|
84
86
|
)
|
|
85
|
-
updated_at:
|
|
87
|
+
updated_at: datetime | None = Field(
|
|
86
88
|
default_factory=lambda: datetime.now(timezone.utc)
|
|
87
89
|
)
|
|
88
90
|
|
|
89
91
|
# Additional data and content stream
|
|
90
|
-
additional_metadata:
|
|
91
|
-
content:
|
|
92
|
-
|
|
92
|
+
additional_metadata: dict[str, Any] = Field(default_factory=dict)
|
|
93
|
+
content: ContentStream | None = Field(default=None, exclude=True)
|
|
94
|
+
|
|
95
|
+
content_bytes: bytes | None = Field(
|
|
93
96
|
default=None,
|
|
94
|
-
description="
|
|
95
|
-
"
|
|
96
|
-
"issues. For larger content, ensure calling from concrete "
|
|
97
|
-
"implementations (ie. outside workflows and use-cases) and use "
|
|
98
|
-
"content field instead.",
|
|
97
|
+
description="Raw content as bytes for cases where direct in-memory "
|
|
98
|
+
"binary payloads are preferred over ContentStream.",
|
|
99
99
|
)
|
|
100
100
|
|
|
101
101
|
@field_validator("document_id")
|
|
@@ -122,29 +122,22 @@ class Document(BaseModel):
|
|
|
122
122
|
@field_validator("content_multihash")
|
|
123
123
|
@classmethod
|
|
124
124
|
def content_multihash_must_not_be_empty(cls, v: str) -> str:
|
|
125
|
-
# TODO: actually validate the multihash against the content?
|
|
126
125
|
if not v or not v.strip():
|
|
127
126
|
raise ValueError("Content multihash cannot be empty")
|
|
128
127
|
return v.strip()
|
|
129
128
|
|
|
130
129
|
@model_validator(mode="after")
|
|
131
130
|
def validate_content_fields(self, info: ValidationInfo) -> "Document":
|
|
132
|
-
"""Ensure document has
|
|
133
|
-
|
|
131
|
+
"""Ensure document has at least content, or content_bytes."""
|
|
132
|
+
|
|
133
|
+
# Skip validation in Temporal deserialization context
|
|
134
134
|
if info.context and info.context.get("temporal_validation"):
|
|
135
135
|
return self
|
|
136
136
|
|
|
137
|
-
# Normal validation for direct instantiation
|
|
138
137
|
has_content = self.content is not None
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if has_content
|
|
142
|
-
raise ValueError(
|
|
143
|
-
|
|
144
|
-
"Provide only one."
|
|
145
|
-
)
|
|
146
|
-
elif not has_content and not has_content_string:
|
|
147
|
-
raise ValueError(
|
|
148
|
-
"Document must have either content or content_string. " "Provide one."
|
|
149
|
-
)
|
|
138
|
+
has_content_bytes = self.content_bytes is not None
|
|
139
|
+
|
|
140
|
+
if not (has_content or has_content_bytes):
|
|
141
|
+
raise ValueError("Document must have one of: content, or content_bytes.")
|
|
142
|
+
|
|
150
143
|
return self
|
|
@@ -8,14 +8,15 @@ Document domain objects with sensible defaults.
|
|
|
8
8
|
import io
|
|
9
9
|
from datetime import datetime, timezone
|
|
10
10
|
from typing import Any
|
|
11
|
+
|
|
11
12
|
from factory.base import Factory
|
|
12
|
-
from factory.faker import Faker
|
|
13
13
|
from factory.declarations import LazyAttribute, LazyFunction
|
|
14
|
+
from factory.faker import Faker
|
|
14
15
|
|
|
15
|
-
from julee.domain.models.document import Document, DocumentStatus
|
|
16
16
|
from julee.domain.models.custom_fields.content_stream import (
|
|
17
17
|
ContentStream,
|
|
18
18
|
)
|
|
19
|
+
from julee.domain.models.document import Document, DocumentStatus
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
# Helper functions to generate content bytes consistently
|
|
@@ -19,11 +19,14 @@ Design decisions documented:
|
|
|
19
19
|
- Documents act as readable streams with standard methods
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
-
import pytest
|
|
23
22
|
import json
|
|
24
23
|
|
|
24
|
+
import pytest
|
|
25
|
+
from pydantic import ValidationError
|
|
26
|
+
|
|
25
27
|
from julee.domain.models.document import Document
|
|
26
|
-
|
|
28
|
+
|
|
29
|
+
from .factories import ContentStreamFactory, DocumentFactory
|
|
27
30
|
|
|
28
31
|
|
|
29
32
|
class TestDocumentInstantiation:
|
|
@@ -164,7 +167,7 @@ class TestDocumentInstantiation:
|
|
|
164
167
|
assert doc.content_multihash.strip() == multihash.strip()
|
|
165
168
|
else:
|
|
166
169
|
# Should raise validation error
|
|
167
|
-
with pytest.raises(
|
|
170
|
+
with pytest.raises((ValueError, ValidationError)):
|
|
168
171
|
Document(
|
|
169
172
|
document_id=document_id,
|
|
170
173
|
original_filename=original_filename,
|
|
@@ -200,32 +203,12 @@ class TestDocumentSerialization:
|
|
|
200
203
|
|
|
201
204
|
|
|
202
205
|
class TestDocumentContentValidation:
|
|
203
|
-
"""Test Document content and
|
|
204
|
-
|
|
205
|
-
def test_document_with_both_content_and_content_string_fails(
|
|
206
|
-
self,
|
|
207
|
-
) -> None:
|
|
208
|
-
"""Test that both content and content_string raises error."""
|
|
209
|
-
content_stream = ContentStreamFactory.build()
|
|
210
|
-
content_string = '{"type": "string"}'
|
|
211
|
-
|
|
212
|
-
with pytest.raises(
|
|
213
|
-
ValueError, match="cannot have both content and content_string"
|
|
214
|
-
):
|
|
215
|
-
Document(
|
|
216
|
-
document_id="test-doc-both",
|
|
217
|
-
original_filename="both.json",
|
|
218
|
-
content_type="application/json",
|
|
219
|
-
size_bytes=100,
|
|
220
|
-
content_multihash="test_hash",
|
|
221
|
-
content=content_stream,
|
|
222
|
-
content_string=content_string,
|
|
223
|
-
)
|
|
206
|
+
"""Test Document content and content_bytes validation rules."""
|
|
224
207
|
|
|
225
|
-
def
|
|
226
|
-
"""Test that no content or
|
|
208
|
+
def test_document_without_content_or_content_bytes_fails(self) -> None:
|
|
209
|
+
"""Test that no content or content_bytes raises error."""
|
|
227
210
|
with pytest.raises(
|
|
228
|
-
ValueError, match="must have
|
|
211
|
+
ValueError, match="must have one of: content, or content_bytes."
|
|
229
212
|
):
|
|
230
213
|
Document(
|
|
231
214
|
document_id="test-doc-no-content",
|
|
@@ -234,7 +217,7 @@ class TestDocumentContentValidation:
|
|
|
234
217
|
size_bytes=100,
|
|
235
218
|
content_multihash="test_hash",
|
|
236
219
|
content=None,
|
|
237
|
-
|
|
220
|
+
content_bytes=None,
|
|
238
221
|
)
|
|
239
222
|
|
|
240
223
|
def test_document_with_content_only_succeeds(self) -> None:
|
|
@@ -248,15 +231,15 @@ class TestDocumentContentValidation:
|
|
|
248
231
|
size_bytes=100,
|
|
249
232
|
content_multihash="test_hash",
|
|
250
233
|
content=content_stream,
|
|
251
|
-
|
|
234
|
+
content_bytes=None,
|
|
252
235
|
)
|
|
253
236
|
|
|
254
237
|
assert doc.content is not None
|
|
255
|
-
assert doc.
|
|
238
|
+
assert doc.content_bytes is None
|
|
256
239
|
|
|
257
|
-
def
|
|
258
|
-
"""Test that document with only
|
|
259
|
-
|
|
240
|
+
def test_document_with_content_bytes_only_succeeds(self) -> None:
|
|
241
|
+
"""Test that document with only content_bytes field succeeds."""
|
|
242
|
+
content_bytes = b'{"type": "string"}'
|
|
260
243
|
|
|
261
244
|
doc = Document(
|
|
262
245
|
document_id="test-doc-string",
|
|
@@ -265,11 +248,11 @@ class TestDocumentContentValidation:
|
|
|
265
248
|
size_bytes=100,
|
|
266
249
|
content_multihash="test_hash",
|
|
267
250
|
content=None,
|
|
268
|
-
|
|
251
|
+
content_bytes=content_bytes,
|
|
269
252
|
)
|
|
270
253
|
|
|
271
254
|
assert doc.content is None
|
|
272
|
-
assert doc.
|
|
255
|
+
assert doc.content_bytes == content_bytes
|
|
273
256
|
|
|
274
257
|
def test_document_deserialization_with_empty_content_succeeds(
|
|
275
258
|
self,
|
|
@@ -284,7 +267,7 @@ class TestDocumentContentValidation:
|
|
|
284
267
|
"size_bytes": 100,
|
|
285
268
|
"content_multihash": "test_hash",
|
|
286
269
|
"content": None,
|
|
287
|
-
"
|
|
270
|
+
"content_bytes": None,
|
|
288
271
|
}
|
|
289
272
|
|
|
290
273
|
# Should succeed with temporal_validation context
|
|
@@ -294,4 +277,4 @@ class TestDocumentContentValidation:
|
|
|
294
277
|
|
|
295
278
|
assert doc.document_id == "test-temporal"
|
|
296
279
|
assert doc.content is None
|
|
297
|
-
assert doc.
|
|
280
|
+
assert doc.content_bytes is None
|
|
@@ -13,11 +13,11 @@ All domain models use Pydantic BaseModel for validation, serialization,
|
|
|
13
13
|
and type safety, following the patterns established in the sample project.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
-
from pydantic import BaseModel, Field, field_validator
|
|
17
|
-
from typing import Optional
|
|
18
16
|
from datetime import datetime, timezone
|
|
19
17
|
from enum import Enum
|
|
20
18
|
|
|
19
|
+
from pydantic import BaseModel, Field, field_validator
|
|
20
|
+
|
|
21
21
|
|
|
22
22
|
class ServiceApi(str, Enum):
|
|
23
23
|
"""Supported knowledge service APIs."""
|
|
@@ -48,10 +48,10 @@ class KnowledgeServiceConfig(BaseModel):
|
|
|
48
48
|
)
|
|
49
49
|
|
|
50
50
|
# Timestamps
|
|
51
|
-
created_at:
|
|
51
|
+
created_at: datetime | None = Field(
|
|
52
52
|
default_factory=lambda: datetime.now(timezone.utc)
|
|
53
53
|
)
|
|
54
|
-
updated_at:
|
|
54
|
+
updated_at: datetime | None = Field(
|
|
55
55
|
default_factory=lambda: datetime.now(timezone.utc)
|
|
56
56
|
)
|
|
57
57
|
|
|
@@ -17,11 +17,11 @@ All domain models use Pydantic BaseModel for validation, serialization,
|
|
|
17
17
|
and type safety, following the patterns established in the sample project.
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
|
-
from pydantic import BaseModel, Field, field_validator
|
|
21
|
-
from typing import Optional, List, Tuple
|
|
22
20
|
from datetime import datetime, timezone
|
|
23
21
|
from enum import Enum
|
|
24
22
|
|
|
23
|
+
from pydantic import BaseModel, Field, field_validator
|
|
24
|
+
|
|
25
25
|
|
|
26
26
|
class DocumentPolicyValidationStatus(str, Enum):
|
|
27
27
|
"""Status of a document policy validation process."""
|
|
@@ -70,7 +70,7 @@ class DocumentPolicyValidation(BaseModel):
|
|
|
70
70
|
status: DocumentPolicyValidationStatus = DocumentPolicyValidationStatus.PENDING
|
|
71
71
|
|
|
72
72
|
# Initial validation results
|
|
73
|
-
validation_scores:
|
|
73
|
+
validation_scores: list[tuple[str, int]] = Field(
|
|
74
74
|
default_factory=list,
|
|
75
75
|
description="List of (knowledge_service_query_id, actual_score) "
|
|
76
76
|
"tuples representing the scores achieved during initial validation. "
|
|
@@ -78,13 +78,13 @@ class DocumentPolicyValidation(BaseModel):
|
|
|
78
78
|
)
|
|
79
79
|
|
|
80
80
|
# Transformation results (if applicable)
|
|
81
|
-
transformed_document_id:
|
|
81
|
+
transformed_document_id: str | None = Field(
|
|
82
82
|
default=None,
|
|
83
83
|
description="ID of the document after transformations have been "
|
|
84
84
|
"applied. Only present if the policy includes transformation queries "
|
|
85
85
|
"and they were executed",
|
|
86
86
|
)
|
|
87
|
-
post_transform_validation_scores:
|
|
87
|
+
post_transform_validation_scores: list[tuple[str, int]] | None = Field(
|
|
88
88
|
default=None,
|
|
89
89
|
description="List of (knowledge_service_query_id, actual_score) "
|
|
90
90
|
"tuples representing scores achieved after transformation. "
|
|
@@ -93,19 +93,19 @@ class DocumentPolicyValidation(BaseModel):
|
|
|
93
93
|
)
|
|
94
94
|
|
|
95
95
|
# Validation metadata
|
|
96
|
-
started_at:
|
|
96
|
+
started_at: datetime | None = Field(
|
|
97
97
|
default_factory=lambda: datetime.now(timezone.utc),
|
|
98
98
|
description="When the validation process was initiated",
|
|
99
99
|
)
|
|
100
|
-
completed_at:
|
|
100
|
+
completed_at: datetime | None = Field(
|
|
101
101
|
default=None, description="When the validation process completed"
|
|
102
102
|
)
|
|
103
|
-
error_message:
|
|
103
|
+
error_message: str | None = Field(
|
|
104
104
|
default=None, description="Error message if validation process failed"
|
|
105
105
|
)
|
|
106
106
|
|
|
107
107
|
# Results summary
|
|
108
|
-
passed:
|
|
108
|
+
passed: bool | None = Field(
|
|
109
109
|
default=None,
|
|
110
110
|
description="Whether the document passed policy validation. "
|
|
111
111
|
"None while validation is in progress, True/False when complete",
|
|
@@ -128,8 +128,8 @@ class DocumentPolicyValidation(BaseModel):
|
|
|
128
128
|
@field_validator("validation_scores")
|
|
129
129
|
@classmethod
|
|
130
130
|
def validation_scores_must_be_valid(
|
|
131
|
-
cls, v:
|
|
132
|
-
) ->
|
|
131
|
+
cls, v: list[tuple[str, int]]
|
|
132
|
+
) -> list[tuple[str, int]]:
|
|
133
133
|
if not isinstance(v, list):
|
|
134
134
|
raise ValueError("Validation scores must be a list")
|
|
135
135
|
|
|
@@ -142,8 +142,8 @@ class DocumentPolicyValidation(BaseModel):
|
|
|
142
142
|
@field_validator("post_transform_validation_scores")
|
|
143
143
|
@classmethod
|
|
144
144
|
def post_transform_scores_must_be_valid(
|
|
145
|
-
cls, v:
|
|
146
|
-
) ->
|
|
145
|
+
cls, v: list[tuple[str, int]] | None
|
|
146
|
+
) -> list[tuple[str, int]] | None:
|
|
147
147
|
if v is None:
|
|
148
148
|
return v
|
|
149
149
|
|
|
@@ -158,7 +158,7 @@ class DocumentPolicyValidation(BaseModel):
|
|
|
158
158
|
|
|
159
159
|
@field_validator("error_message")
|
|
160
160
|
@classmethod
|
|
161
|
-
def error_message_must_be_valid(cls, v:
|
|
161
|
+
def error_message_must_be_valid(cls, v: str | None) -> str | None:
|
|
162
162
|
if v is None:
|
|
163
163
|
return v
|
|
164
164
|
if not isinstance(v, str):
|
|
@@ -167,7 +167,7 @@ class DocumentPolicyValidation(BaseModel):
|
|
|
167
167
|
|
|
168
168
|
@field_validator("transformed_document_id")
|
|
169
169
|
@classmethod
|
|
170
|
-
def transformed_document_id_must_be_valid(cls, v:
|
|
170
|
+
def transformed_document_id_must_be_valid(cls, v: str | None) -> str | None:
|
|
171
171
|
if v is None:
|
|
172
172
|
return v
|
|
173
173
|
if not isinstance(v, str) or not v.strip():
|
|
@@ -178,8 +178,8 @@ class DocumentPolicyValidation(BaseModel):
|
|
|
178
178
|
|
|
179
179
|
@classmethod
|
|
180
180
|
def _validate_score_tuples(
|
|
181
|
-
cls, scores:
|
|
182
|
-
) ->
|
|
181
|
+
cls, scores: list[tuple[str, int]], field_name: str
|
|
182
|
+
) -> list[tuple[str, int]]:
|
|
183
183
|
"""Helper method to validate score tuple lists."""
|
|
184
184
|
validated_scores = []
|
|
185
185
|
query_ids_seen = set()
|
|
@@ -13,11 +13,11 @@ All domain models use Pydantic BaseModel for validation, serialization,
|
|
|
13
13
|
and type safety, following the patterns established in the sample project.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
-
from pydantic import BaseModel, Field, field_validator
|
|
17
|
-
from typing import Optional, List, Tuple
|
|
18
16
|
from datetime import datetime, timezone
|
|
19
17
|
from enum import Enum
|
|
20
18
|
|
|
19
|
+
from pydantic import BaseModel, Field, field_validator
|
|
20
|
+
|
|
21
21
|
|
|
22
22
|
class PolicyStatus(str, Enum):
|
|
23
23
|
"""Status of a policy configuration."""
|
|
@@ -55,12 +55,12 @@ class Policy(BaseModel):
|
|
|
55
55
|
|
|
56
56
|
# Policy configuration
|
|
57
57
|
status: PolicyStatus = PolicyStatus.ACTIVE
|
|
58
|
-
validation_scores:
|
|
58
|
+
validation_scores: list[tuple[str, int]] = Field(
|
|
59
59
|
description="List of (knowledge_service_query_id, required_score) "
|
|
60
60
|
"tuples where required_score is between 0 and 100. All scores "
|
|
61
61
|
"must be met or exceeded for the policy to pass"
|
|
62
62
|
)
|
|
63
|
-
transformation_queries:
|
|
63
|
+
transformation_queries: list[str] | None = Field(
|
|
64
64
|
default=None,
|
|
65
65
|
description="Optional list of knowledge service query IDs for "
|
|
66
66
|
"transformations to apply before re-validation. If not provided "
|
|
@@ -69,10 +69,10 @@ class Policy(BaseModel):
|
|
|
69
69
|
|
|
70
70
|
# Policy metadata
|
|
71
71
|
version: str = Field(default="0.1.0", description="Policy version")
|
|
72
|
-
created_at:
|
|
72
|
+
created_at: datetime | None = Field(
|
|
73
73
|
default_factory=lambda: datetime.now(timezone.utc)
|
|
74
74
|
)
|
|
75
|
-
updated_at:
|
|
75
|
+
updated_at: datetime | None = Field(default=None)
|
|
76
76
|
|
|
77
77
|
@field_validator("policy_id")
|
|
78
78
|
@classmethod
|
|
@@ -98,8 +98,8 @@ class Policy(BaseModel):
|
|
|
98
98
|
@field_validator("validation_scores")
|
|
99
99
|
@classmethod
|
|
100
100
|
def validation_scores_must_be_valid(
|
|
101
|
-
cls, v:
|
|
102
|
-
) ->
|
|
101
|
+
cls, v: list[tuple[str, int]]
|
|
102
|
+
) -> list[tuple[str, int]]:
|
|
103
103
|
if not isinstance(v, list):
|
|
104
104
|
raise ValueError("Validation scores must be a list")
|
|
105
105
|
|
|
@@ -147,8 +147,8 @@ class Policy(BaseModel):
|
|
|
147
147
|
@field_validator("transformation_queries")
|
|
148
148
|
@classmethod
|
|
149
149
|
def transformation_queries_must_be_valid(
|
|
150
|
-
cls, v:
|
|
151
|
-
) ->
|
|
150
|
+
cls, v: list[str] | None
|
|
151
|
+
) -> list[str] | None:
|
|
152
152
|
if v is None:
|
|
153
153
|
return v
|
|
154
154
|
|
|
@@ -6,9 +6,10 @@ Policy domain objects with sensible defaults.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from datetime import datetime, timezone
|
|
9
|
+
|
|
9
10
|
from factory.base import Factory
|
|
10
|
-
from factory.faker import Faker
|
|
11
11
|
from factory.declarations import LazyFunction
|
|
12
|
+
from factory.faker import Faker
|
|
12
13
|
|
|
13
14
|
from julee.domain.models.policy import (
|
|
14
15
|
DocumentPolicyValidation,
|
|
@@ -13,14 +13,16 @@ Tests focus on:
|
|
|
13
13
|
- Edge cases and error conditions
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
-
import pytest
|
|
17
16
|
from datetime import datetime, timezone
|
|
17
|
+
|
|
18
|
+
import pytest
|
|
18
19
|
from pydantic import ValidationError
|
|
19
20
|
|
|
20
21
|
from julee.domain.models.policy import (
|
|
21
22
|
DocumentPolicyValidation,
|
|
22
23
|
DocumentPolicyValidationStatus,
|
|
23
24
|
)
|
|
25
|
+
|
|
24
26
|
from .factories import DocumentPolicyValidationFactory
|
|
25
27
|
|
|
26
28
|
|
|
@@ -5,8 +5,9 @@ These tests verify the behavior of Policy domain objects,
|
|
|
5
5
|
including validation, serialization, and business logic.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
import pytest
|
|
9
8
|
from datetime import datetime, timezone
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
10
11
|
from pydantic import ValidationError
|
|
11
12
|
|
|
12
13
|
from julee.domain.models.policy import (
|
|
@@ -6,14 +6,14 @@ Extract, Assemble, Publish workflow, following the Clean Architecture
|
|
|
6
6
|
patterns established in the Fun-Police framework.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
from .base import BaseRepository
|
|
10
|
-
from .document import DocumentRepository
|
|
11
9
|
from .assembly import AssemblyRepository
|
|
12
10
|
from .assembly_specification import AssemblySpecificationRepository
|
|
11
|
+
from .base import BaseRepository
|
|
12
|
+
from .document import DocumentRepository
|
|
13
|
+
from .document_policy_validation import DocumentPolicyValidationRepository
|
|
13
14
|
from .knowledge_service_config import KnowledgeServiceConfigRepository
|
|
14
15
|
from .knowledge_service_query import KnowledgeServiceQueryRepository
|
|
15
16
|
from .policy import PolicyRepository
|
|
16
|
-
from .document_policy_validation import DocumentPolicyValidationRepository
|
|
17
17
|
|
|
18
18
|
__all__ = [
|
|
19
19
|
"BaseRepository",
|
|
@@ -27,8 +27,10 @@ In Temporal workflow contexts, these protocols are implemented by workflow
|
|
|
27
27
|
stubs that delegate to activities for durability and proper error handling.
|
|
28
28
|
"""
|
|
29
29
|
|
|
30
|
-
from typing import
|
|
30
|
+
from typing import Protocol, runtime_checkable
|
|
31
|
+
|
|
31
32
|
from julee.domain.models import Assembly
|
|
33
|
+
|
|
32
34
|
from .base import BaseRepository
|
|
33
35
|
|
|
34
36
|
|
|
@@ -30,9 +30,11 @@ stubs that delegate to activities for durability and proper error handling.
|
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
32
|
from typing import Protocol, runtime_checkable
|
|
33
|
+
|
|
33
34
|
from julee.domain.models.assembly_specification import (
|
|
34
35
|
AssemblySpecification,
|
|
35
36
|
)
|
|
37
|
+
|
|
36
38
|
from .base import BaseRepository
|
|
37
39
|
|
|
38
40
|
|
|
@@ -23,7 +23,8 @@ In Temporal workflow contexts, these protocols are implemented by workflow
|
|
|
23
23
|
stubs that delegate to activities for durability and proper error handling.
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
|
-
from typing import Protocol,
|
|
26
|
+
from typing import Protocol, TypeVar, runtime_checkable
|
|
27
|
+
|
|
27
28
|
from pydantic import BaseModel
|
|
28
29
|
|
|
29
30
|
# Type variable bound to Pydantic BaseModel for domain entities
|
|
@@ -42,7 +43,7 @@ class BaseRepository(Protocol[T]):
|
|
|
42
43
|
T: The domain entity type (must extend Pydantic BaseModel)
|
|
43
44
|
"""
|
|
44
45
|
|
|
45
|
-
async def get(self, entity_id: str) ->
|
|
46
|
+
async def get(self, entity_id: str) -> T | None:
|
|
46
47
|
"""Retrieve an entity by ID.
|
|
47
48
|
|
|
48
49
|
Args:
|
|
@@ -60,7 +61,7 @@ class BaseRepository(Protocol[T]):
|
|
|
60
61
|
"""
|
|
61
62
|
...
|
|
62
63
|
|
|
63
|
-
async def get_many(self, entity_ids:
|
|
64
|
+
async def get_many(self, entity_ids: list[str]) -> dict[str, T | None]:
|
|
64
65
|
"""Retrieve multiple entities by ID.
|
|
65
66
|
|
|
66
67
|
Args:
|
|
@@ -103,7 +104,7 @@ class BaseRepository(Protocol[T]):
|
|
|
103
104
|
"""
|
|
104
105
|
...
|
|
105
106
|
|
|
106
|
-
async def list_all(self) ->
|
|
107
|
+
async def list_all(self) -> list[T]:
|
|
107
108
|
"""List all entities.
|
|
108
109
|
|
|
109
110
|
Returns:
|
|
@@ -29,8 +29,10 @@ In Temporal workflow contexts, these protocols are implemented by workflow
|
|
|
29
29
|
stubs that delegate to activities for durability and proper error handling.
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
|
-
from typing import
|
|
32
|
+
from typing import Protocol, runtime_checkable
|
|
33
|
+
|
|
33
34
|
from julee.domain.models import Document
|
|
35
|
+
|
|
34
36
|
from .base import BaseRepository
|
|
35
37
|
|
|
36
38
|
|
|
@@ -29,8 +29,10 @@ In Temporal workflow contexts, these protocols are implemented by workflow
|
|
|
29
29
|
stubs that delegate to activities for durability and proper error handling.
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
|
-
from typing import
|
|
32
|
+
from typing import Protocol, runtime_checkable
|
|
33
|
+
|
|
33
34
|
from julee.domain.models.policy import DocumentPolicyValidation
|
|
35
|
+
|
|
34
36
|
from .base import BaseRepository
|
|
35
37
|
|
|
36
38
|
|
|
@@ -31,9 +31,11 @@ stubs that delegate to activities for durability and proper error handling.
|
|
|
31
31
|
"""
|
|
32
32
|
|
|
33
33
|
from typing import Protocol, runtime_checkable
|
|
34
|
+
|
|
34
35
|
from julee.domain.models.knowledge_service_config import (
|
|
35
36
|
KnowledgeServiceConfig,
|
|
36
37
|
)
|
|
38
|
+
|
|
37
39
|
from .base import BaseRepository
|
|
38
40
|
|
|
39
41
|
|
|
@@ -29,8 +29,10 @@ In Temporal workflow contexts, these protocols are implemented by workflow
|
|
|
29
29
|
stubs that delegate to activities for durability and proper error handling.
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
|
-
from typing import
|
|
32
|
+
from typing import Protocol, runtime_checkable
|
|
33
|
+
|
|
33
34
|
from julee.domain.models import Policy
|
|
35
|
+
|
|
34
36
|
from .base import BaseRepository
|
|
35
37
|
|
|
36
38
|
|
|
@@ -7,8 +7,9 @@ following the patterns established in the sample use cases.
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
import logging
|
|
10
|
+
from collections.abc import Awaitable, Callable
|
|
10
11
|
from functools import wraps
|
|
11
|
-
from typing import Any,
|
|
12
|
+
from typing import Any, TypeVar
|
|
12
13
|
|
|
13
14
|
logger = logging.getLogger(__name__)
|
|
14
15
|
|
|
@@ -17,7 +18,7 @@ F = TypeVar("F", bound=Callable[..., Awaitable[Any]])
|
|
|
17
18
|
|
|
18
19
|
def try_use_case_step(
|
|
19
20
|
step_name: str,
|
|
20
|
-
extra_context:
|
|
21
|
+
extra_context: dict[str, Any] | None = None,
|
|
21
22
|
) -> Callable[[F], F]:
|
|
22
23
|
"""
|
|
23
24
|
Decorator that wraps use case steps with consistent error handling and
|