julee 0.1.2__py3-none-any.whl → 0.1.3__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 +5 -4
- 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 +82 -0
- julee/docs/sphinx_hcd/accelerators.py +1078 -0
- julee/docs/sphinx_hcd/apps.py +499 -0
- julee/docs/sphinx_hcd/config.py +148 -0
- julee/docs/sphinx_hcd/epics.py +448 -0
- julee/docs/sphinx_hcd/integrations.py +306 -0
- julee/docs/sphinx_hcd/journeys.py +783 -0
- julee/docs/sphinx_hcd/personas.py +435 -0
- julee/docs/sphinx_hcd/stories.py +932 -0
- julee/docs/sphinx_hcd/utils.py +180 -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 +12 -10
- julee/domain/models/document/tests/factories.py +3 -2
- julee/domain/models/document/tests/test_document.py +6 -3
- 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 +13 -12
- julee/domain/use_cases/initialize_system_data.py +13 -13
- 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/maintenance/__init__.py +1 -0
- julee/maintenance/release.py +188 -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 +8 -7
- 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 +6 -4
- 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 +11 -11
- 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 +7 -6
- 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.3.dist-info}/METADATA +2 -1
- julee-0.1.3.dist-info/RECORD +197 -0
- julee-0.1.2.dist-info/RECORD +0 -161
- {julee-0.1.2.dist-info → julee-0.1.3.dist-info}/WHEEL +0 -0
- {julee-0.1.2.dist-info → julee-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {julee-0.1.2.dist-info → julee-0.1.3.dist-info}/top_level.txt +0 -0
|
@@ -6,19 +6,22 @@ KnowledgeService protocol, verifying file registration storage and
|
|
|
6
6
|
canned query response functionality.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
import
|
|
9
|
+
import io
|
|
10
10
|
from datetime import datetime, timezone
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
from julee.domain.models.document import Document, DocumentStatus
|
|
15
|
-
from julee.domain.models.knowledge_service_config import ServiceApi
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
16
14
|
from julee.domain.models.custom_fields.content_stream import (
|
|
17
15
|
ContentStream,
|
|
18
16
|
)
|
|
17
|
+
from julee.domain.models.document import Document, DocumentStatus
|
|
18
|
+
from julee.domain.models.knowledge_service_config import (
|
|
19
|
+
KnowledgeServiceConfig,
|
|
20
|
+
ServiceApi,
|
|
21
|
+
)
|
|
22
|
+
|
|
19
23
|
from ..knowledge_service import QueryResult
|
|
20
24
|
from .knowledge_service import MemoryKnowledgeService
|
|
21
|
-
import io
|
|
22
25
|
|
|
23
26
|
|
|
24
27
|
@pytest.fixture
|
|
@@ -5,25 +5,26 @@ This module contains tests for the factory function that creates
|
|
|
5
5
|
KnowledgeService implementations based on configuration.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
import io
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
|
+
|
|
8
11
|
import pytest
|
|
9
12
|
|
|
10
|
-
from julee.domain.models.knowledge_service_config import (
|
|
11
|
-
KnowledgeServiceConfig,
|
|
12
|
-
)
|
|
13
|
-
from julee.domain.models.document import Document, DocumentStatus
|
|
14
|
-
from julee.domain.models.knowledge_service_config import ServiceApi
|
|
15
13
|
from julee.domain.models.custom_fields.content_stream import (
|
|
16
14
|
ContentStream,
|
|
17
15
|
)
|
|
18
|
-
from julee.
|
|
19
|
-
from julee.
|
|
20
|
-
|
|
16
|
+
from julee.domain.models.document import Document, DocumentStatus
|
|
17
|
+
from julee.domain.models.knowledge_service_config import (
|
|
18
|
+
KnowledgeServiceConfig,
|
|
19
|
+
ServiceApi,
|
|
21
20
|
)
|
|
21
|
+
from julee.services.knowledge_service import ensure_knowledge_service
|
|
22
22
|
from julee.services.knowledge_service.anthropic import (
|
|
23
23
|
AnthropicKnowledgeService,
|
|
24
24
|
)
|
|
25
|
-
import
|
|
26
|
-
|
|
25
|
+
from julee.services.knowledge_service.factory import (
|
|
26
|
+
knowledge_service_factory,
|
|
27
|
+
)
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
@pytest.fixture
|
|
@@ -13,23 +13,23 @@ The class follows the naming pattern documented in systemPatterns.org:
|
|
|
13
13
|
|
|
14
14
|
import io
|
|
15
15
|
import logging
|
|
16
|
+
|
|
16
17
|
from typing_extensions import override
|
|
17
18
|
|
|
18
|
-
from julee.
|
|
19
|
-
from julee.services.knowledge_service.factory import (
|
|
20
|
-
ConfigurableKnowledgeService,
|
|
21
|
-
)
|
|
22
|
-
from julee.domain.repositories.document import DocumentRepository
|
|
19
|
+
from julee.domain.models.document import Document
|
|
23
20
|
from julee.domain.models.knowledge_service_config import (
|
|
24
21
|
KnowledgeServiceConfig,
|
|
25
22
|
)
|
|
26
|
-
from julee.domain.
|
|
27
|
-
from
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
from julee.domain.repositories.document import DocumentRepository
|
|
24
|
+
from julee.services.knowledge_service.factory import (
|
|
25
|
+
ConfigurableKnowledgeService,
|
|
26
|
+
)
|
|
30
27
|
from julee.services.temporal.activity_names import (
|
|
31
28
|
KNOWLEDGE_SERVICE_ACTIVITY_BASE,
|
|
32
29
|
)
|
|
30
|
+
from julee.util.temporal.decorators import temporal_activity_registration
|
|
31
|
+
|
|
32
|
+
from ..knowledge_service import FileRegistrationResult
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
@temporal_activity_registration(KNOWLEDGE_SERVICE_ACTIVITY_BASE)
|
|
@@ -79,7 +79,7 @@ class TemporalKnowledgeService(ConfigurableKnowledgeService):
|
|
|
79
79
|
return await super().register_file(config, document)
|
|
80
80
|
|
|
81
81
|
|
|
82
|
-
# Export the temporal service
|
|
82
|
+
# Export the temporal service classes for use in worker.py
|
|
83
83
|
__all__ = [
|
|
84
84
|
"TemporalKnowledgeService",
|
|
85
85
|
"KNOWLEDGE_SERVICE_ACTIVITY_BASE",
|
|
@@ -11,13 +11,13 @@ workflow.execute_activity() with the appropriate activity names, timeouts,
|
|
|
11
11
|
and retry policies.
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
from julee.util.temporal.decorators import temporal_workflow_proxy
|
|
15
14
|
from julee.services.knowledge_service import KnowledgeService
|
|
16
15
|
|
|
17
16
|
# Import activity name bases from shared module
|
|
18
17
|
from julee.services.temporal.activity_names import (
|
|
19
18
|
KNOWLEDGE_SERVICE_ACTIVITY_BASE,
|
|
20
19
|
)
|
|
20
|
+
from julee.util.temporal.decorators import temporal_workflow_proxy
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
@temporal_workflow_proxy(
|
|
@@ -35,7 +35,7 @@ class WorkflowKnowledgeServiceProxy(KnowledgeService):
|
|
|
35
35
|
pass
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
# Export the workflow proxy
|
|
38
|
+
# Export the workflow proxy classes
|
|
39
39
|
__all__ = [
|
|
40
40
|
"WorkflowKnowledgeServiceProxy",
|
|
41
41
|
]
|
julee/util/domain.py
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
+
from datetime import datetime, timezone
|
|
2
|
+
|
|
1
3
|
from pydantic import (
|
|
2
4
|
BaseModel,
|
|
3
5
|
Field,
|
|
4
6
|
field_validator,
|
|
5
7
|
)
|
|
6
|
-
from typing import Optional, Dict
|
|
7
|
-
from datetime import datetime, timezone
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class FileMetadata(BaseModel):
|
|
11
11
|
"""Metadata about a stored file."""
|
|
12
12
|
|
|
13
13
|
file_id: str
|
|
14
|
-
filename:
|
|
15
|
-
content_type:
|
|
16
|
-
size_bytes:
|
|
14
|
+
filename: str | None = None
|
|
15
|
+
content_type: str | None = None
|
|
16
|
+
size_bytes: int | None = None
|
|
17
17
|
uploaded_at: str = Field(
|
|
18
18
|
default_factory=lambda: datetime.now(timezone.utc).isoformat()
|
|
19
19
|
)
|
|
20
|
-
metadata:
|
|
20
|
+
metadata: dict[str, str] = Field(default_factory=dict)
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class FileUploadArgs(BaseModel):
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import io
|
|
2
2
|
import logging
|
|
3
3
|
import os
|
|
4
|
-
from typing import Optional
|
|
5
4
|
|
|
6
5
|
from minio import Minio # type: ignore[import-untyped]
|
|
7
6
|
from minio.error import S3Error # type: ignore[import-untyped]
|
|
@@ -20,11 +19,11 @@ class MinioFileStorageRepository(FileStorageRepository):
|
|
|
20
19
|
|
|
21
20
|
def __init__(
|
|
22
21
|
self,
|
|
23
|
-
endpoint:
|
|
24
|
-
access_key:
|
|
25
|
-
secret_key:
|
|
22
|
+
endpoint: str | None = None,
|
|
23
|
+
access_key: str | None = None,
|
|
24
|
+
secret_key: str | None = None,
|
|
26
25
|
secure: bool = False,
|
|
27
|
-
bucket_name:
|
|
26
|
+
bucket_name: str | None = None,
|
|
28
27
|
):
|
|
29
28
|
self._endpoint = (
|
|
30
29
|
endpoint
|
|
@@ -48,7 +47,7 @@ class MinioFileStorageRepository(FileStorageRepository):
|
|
|
48
47
|
else os.environ.get("MINIO_BUCKET_NAME", "file-storage")
|
|
49
48
|
)
|
|
50
49
|
|
|
51
|
-
self._client:
|
|
50
|
+
self._client: Minio | None = None
|
|
52
51
|
logger.debug(
|
|
53
52
|
"MinioFileStorageRepository initialized",
|
|
54
53
|
extra={
|
|
@@ -134,7 +133,7 @@ class MinioFileStorageRepository(FileStorageRepository):
|
|
|
134
133
|
)
|
|
135
134
|
raise
|
|
136
135
|
|
|
137
|
-
async def download_file(self, file_id: str) ->
|
|
136
|
+
async def download_file(self, file_id: str) -> bytes | None:
|
|
138
137
|
"""Download a file from Minio storage by its ID."""
|
|
139
138
|
client = await self._get_client()
|
|
140
139
|
logger.info(
|
|
@@ -161,7 +160,7 @@ class MinioFileStorageRepository(FileStorageRepository):
|
|
|
161
160
|
)
|
|
162
161
|
raise
|
|
163
162
|
|
|
164
|
-
async def get_file_metadata(self, file_id: str) ->
|
|
163
|
+
async def get_file_metadata(self, file_id: str) -> FileMetadata | None:
|
|
165
164
|
"""Retrieve metadata for a stored file from Minio."""
|
|
166
165
|
client = await self._get_client()
|
|
167
166
|
logger.info(
|
|
@@ -178,7 +177,7 @@ class MinioFileStorageRepository(FileStorageRepository):
|
|
|
178
177
|
"content_type": stat.content_type,
|
|
179
178
|
},
|
|
180
179
|
)
|
|
181
|
-
uploaded_at_str:
|
|
180
|
+
uploaded_at_str: str | None = (
|
|
182
181
|
stat.last_modified.isoformat() if stat.last_modified else None
|
|
183
182
|
)
|
|
184
183
|
# Extract filename and metadata more explicitly
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from typing import Optional
|
|
3
2
|
|
|
4
3
|
from temporalio.client import Client
|
|
5
4
|
|
|
@@ -19,7 +18,7 @@ class TemporalFileStorageRepository(FileStorageRepository):
|
|
|
19
18
|
def __init__(
|
|
20
19
|
self,
|
|
21
20
|
client: Client,
|
|
22
|
-
concrete_repo:
|
|
21
|
+
concrete_repo: FileStorageRepository | None = None,
|
|
23
22
|
):
|
|
24
23
|
self.client = client
|
|
25
24
|
self.concrete_repo = concrete_repo
|
|
@@ -39,7 +38,7 @@ class TemporalFileStorageRepository(FileStorageRepository):
|
|
|
39
38
|
result = await handle.result()
|
|
40
39
|
return result # type: ignore[no-any-return]
|
|
41
40
|
|
|
42
|
-
async def download_file(self, file_id: str) ->
|
|
41
|
+
async def download_file(self, file_id: str) -> bytes | None:
|
|
43
42
|
"""Download a file via Temporal activity."""
|
|
44
43
|
logger.debug(f"Client calling activity to download file: {file_id}")
|
|
45
44
|
|
|
@@ -53,7 +52,7 @@ class TemporalFileStorageRepository(FileStorageRepository):
|
|
|
53
52
|
result = await handle.result()
|
|
54
53
|
return result # type: ignore[no-any-return]
|
|
55
54
|
|
|
56
|
-
async def get_file_metadata(self, file_id: str) ->
|
|
55
|
+
async def get_file_metadata(self, file_id: str) -> FileMetadata | None:
|
|
57
56
|
"""Retrieve file metadata via Temporal activity."""
|
|
58
57
|
logger.debug(f"Client calling activity to get file metadata: {file_id}")
|
|
59
58
|
|
|
@@ -7,20 +7,20 @@ This allows domain models to implement context-aware validation that can
|
|
|
7
7
|
be more permissive during Temporal serialization/deserialization.
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
-
from typing import Any
|
|
10
|
+
from typing import Any
|
|
11
11
|
|
|
12
|
+
import temporalio.api.common.v1
|
|
12
13
|
from pydantic import TypeAdapter
|
|
13
14
|
from temporalio.contrib.pydantic import (
|
|
14
15
|
PydanticJSONPlainPayloadConverter,
|
|
15
16
|
ToJsonOptions,
|
|
16
17
|
)
|
|
17
18
|
from temporalio.converter import (
|
|
18
|
-
DataConverter,
|
|
19
19
|
CompositePayloadConverter,
|
|
20
|
+
DataConverter,
|
|
20
21
|
DefaultPayloadConverter,
|
|
21
22
|
JSONPlainPayloadConverter,
|
|
22
23
|
)
|
|
23
|
-
import temporalio.api.common.v1
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class TemporalValidationPydanticConverter(PydanticJSONPlainPayloadConverter):
|
|
@@ -36,7 +36,7 @@ class TemporalValidationPydanticConverter(PydanticJSONPlainPayloadConverter):
|
|
|
36
36
|
def from_payload(
|
|
37
37
|
self,
|
|
38
38
|
payload: temporalio.api.common.v1.Payload,
|
|
39
|
-
type_hint:
|
|
39
|
+
type_hint: type | None = None,
|
|
40
40
|
) -> Any:
|
|
41
41
|
"""Deserialize payload with temporal_validation context.
|
|
42
42
|
|
|
@@ -69,7 +69,7 @@ class TemporalValidationPayloadConverter(CompositePayloadConverter):
|
|
|
69
69
|
ensuring all Pydantic models get temporal_validation context.
|
|
70
70
|
"""
|
|
71
71
|
|
|
72
|
-
def __init__(self, to_json_options:
|
|
72
|
+
def __init__(self, to_json_options: ToJsonOptions | None = None) -> None:
|
|
73
73
|
"""Initialize with custom JSON converter adding temporal context."""
|
|
74
74
|
# Create our custom JSON converter with temporal validation
|
|
75
75
|
json_payload_converter = TemporalValidationPydanticConverter(to_json_options)
|
|
@@ -89,7 +89,7 @@ class TemporalValidationPayloadConverter(CompositePayloadConverter):
|
|
|
89
89
|
|
|
90
90
|
|
|
91
91
|
def create_temporal_data_converter(
|
|
92
|
-
to_json_options:
|
|
92
|
+
to_json_options: ToJsonOptions | None = None,
|
|
93
93
|
) -> DataConverter:
|
|
94
94
|
"""Create a data converter with temporal validation support.
|
|
95
95
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from julee.util.temporal.decorators import temporal_activity_registration
|
|
2
1
|
from julee.util.repos.minio.file_storage import MinioFileStorageRepository
|
|
2
|
+
from julee.util.temporal.decorators import temporal_activity_registration
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
@temporal_activity_registration("util.file_storage.minio")
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from typing import Optional
|
|
3
2
|
|
|
4
3
|
from temporalio import workflow
|
|
5
4
|
|
|
@@ -35,7 +34,7 @@ class WorkflowFileStorageRepositoryProxy(FileStorageRepository):
|
|
|
35
34
|
)
|
|
36
35
|
return FileMetadata.model_validate(result)
|
|
37
36
|
|
|
38
|
-
async def download_file(self, file_id: str) ->
|
|
37
|
+
async def download_file(self, file_id: str) -> bytes | None:
|
|
39
38
|
"""Download a file from storage via Temporal activity."""
|
|
40
39
|
logger.debug(f"Workflow calling activity to download file: {file_id}")
|
|
41
40
|
result = await workflow.execute_activity(
|
|
@@ -45,7 +44,7 @@ class WorkflowFileStorageRepositoryProxy(FileStorageRepository):
|
|
|
45
44
|
)
|
|
46
45
|
return result # type: ignore[no-any-return]
|
|
47
46
|
|
|
48
|
-
async def get_file_metadata(self, file_id: str) ->
|
|
47
|
+
async def get_file_metadata(self, file_id: str) -> FileMetadata | None:
|
|
49
48
|
"""Retrieve file metadata via Temporal activity."""
|
|
50
49
|
logger.debug(f"Workflow calling activity to get file metadata: {file_id}")
|
|
51
50
|
result = await workflow.execute_activity(
|
julee/util/repositories.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
from typing import Protocol,
|
|
1
|
+
from typing import Protocol, runtime_checkable
|
|
2
|
+
|
|
2
3
|
from julee.util.domain import FileMetadata, FileUploadArgs
|
|
3
4
|
|
|
4
5
|
|
|
@@ -30,7 +31,7 @@ class FileStorageRepository(Protocol):
|
|
|
30
31
|
"""
|
|
31
32
|
...
|
|
32
33
|
|
|
33
|
-
async def download_file(self, file_id: str) ->
|
|
34
|
+
async def download_file(self, file_id: str) -> bytes | None:
|
|
34
35
|
"""Download a file from storage by its ID.
|
|
35
36
|
|
|
36
37
|
Args:
|
|
@@ -41,7 +42,7 @@ class FileStorageRepository(Protocol):
|
|
|
41
42
|
"""
|
|
42
43
|
...
|
|
43
44
|
|
|
44
|
-
async def get_file_metadata(self, file_id: str) ->
|
|
45
|
+
async def get_file_metadata(self, file_id: str) -> FileMetadata | None:
|
|
45
46
|
"""Retrieve metadata for a stored file.
|
|
46
47
|
|
|
47
48
|
Args:
|
|
@@ -10,12 +10,10 @@ Both reduce boilerplate and ensure consistent patterns.
|
|
|
10
10
|
import functools
|
|
11
11
|
import inspect
|
|
12
12
|
import logging
|
|
13
|
+
from collections.abc import Callable
|
|
13
14
|
from datetime import timedelta
|
|
14
15
|
from typing import (
|
|
15
16
|
Any,
|
|
16
|
-
Callable,
|
|
17
|
-
Optional,
|
|
18
|
-
Type,
|
|
19
17
|
TypeVar,
|
|
20
18
|
get_args,
|
|
21
19
|
get_origin,
|
|
@@ -34,7 +32,7 @@ logger = logging.getLogger(__name__)
|
|
|
34
32
|
T = TypeVar("T")
|
|
35
33
|
|
|
36
34
|
|
|
37
|
-
def _extract_concrete_type_from_base(cls: type) ->
|
|
35
|
+
def _extract_concrete_type_from_base(cls: type) -> type | None:
|
|
38
36
|
"""
|
|
39
37
|
Extract the concrete type argument from a generic base class.
|
|
40
38
|
|
|
@@ -113,7 +111,7 @@ def _substitute_typevar_with_concrete(annotation: Any, concrete_type: type) -> A
|
|
|
113
111
|
|
|
114
112
|
def temporal_activity_registration(
|
|
115
113
|
activity_prefix: str,
|
|
116
|
-
) -> Callable[[
|
|
114
|
+
) -> Callable[[type[T]], type[T]]:
|
|
117
115
|
"""
|
|
118
116
|
Class decorator that wraps async protocol methods as Temporal activities.
|
|
119
117
|
|
|
@@ -139,7 +137,7 @@ def temporal_activity_registration(
|
|
|
139
137
|
# - refund_payment -> "sample.payment_repo.minio.refund_payment"
|
|
140
138
|
"""
|
|
141
139
|
|
|
142
|
-
def decorator(cls:
|
|
140
|
+
def decorator(cls: type[T]) -> type[T]:
|
|
143
141
|
logger.debug(
|
|
144
142
|
f"Applying temporal_activity_registration decorator to {cls.__name__}"
|
|
145
143
|
)
|
|
@@ -180,8 +178,9 @@ def temporal_activity_registration(
|
|
|
180
178
|
return wrapper_method
|
|
181
179
|
|
|
182
180
|
# Create the wrapper and apply activity decorator
|
|
183
|
-
|
|
184
|
-
|
|
181
|
+
wrapped_method = activity.defn(name=activity_name)(
|
|
182
|
+
create_wrapper_method(method, name)
|
|
183
|
+
)
|
|
185
184
|
|
|
186
185
|
# Replace the method on the class with the wrapped version
|
|
187
186
|
setattr(cls, name, wrapped_method)
|
|
@@ -204,8 +203,8 @@ def temporal_activity_registration(
|
|
|
204
203
|
def temporal_workflow_proxy(
|
|
205
204
|
activity_base: str,
|
|
206
205
|
default_timeout_seconds: int = 30,
|
|
207
|
-
retry_methods:
|
|
208
|
-
) -> Callable[[
|
|
206
|
+
retry_methods: list[str] | None = None,
|
|
207
|
+
) -> Callable[[type[T]], type[T]]:
|
|
209
208
|
"""
|
|
210
209
|
Class decorator that automatically creates workflow proxy methods that
|
|
211
210
|
delegate to Temporal activities.
|
|
@@ -238,7 +237,7 @@ def temporal_workflow_proxy(
|
|
|
238
237
|
# - generate_id() -> calls "julee.document_repo.minio.generate_id" with retry
|
|
239
238
|
"""
|
|
240
239
|
|
|
241
|
-
def decorator(cls:
|
|
240
|
+
def decorator(cls: type[T]) -> type[T]:
|
|
242
241
|
logger.debug(f"Applying temporal_workflow_proxy decorator to {cls.__name__}")
|
|
243
242
|
|
|
244
243
|
retry_methods_set = set(retry_methods or [])
|
|
@@ -390,14 +389,17 @@ def temporal_workflow_proxy(
|
|
|
390
389
|
return workflow_method
|
|
391
390
|
|
|
392
391
|
# Create and set the method on the class
|
|
393
|
-
|
|
392
|
+
setattr(
|
|
393
|
+
cls,
|
|
394
394
|
method_name,
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
395
|
+
create_workflow_method(
|
|
396
|
+
method_name,
|
|
397
|
+
needs_validation,
|
|
398
|
+
is_optional,
|
|
399
|
+
inner_type,
|
|
400
|
+
original_method,
|
|
401
|
+
),
|
|
399
402
|
)
|
|
400
|
-
setattr(cls, method_name, workflow_method)
|
|
401
403
|
wrapped_methods.append(method_name)
|
|
402
404
|
|
|
403
405
|
# Always generate __init__ that calls super() for consistent init
|
|
@@ -409,7 +411,7 @@ def temporal_workflow_proxy(
|
|
|
409
411
|
proxy_self.activity_fail_fast_retry_policy = fail_fast_retry_policy
|
|
410
412
|
logger.debug(f"Initialized {cls.__name__}")
|
|
411
413
|
|
|
412
|
-
|
|
414
|
+
cls.__init__ = __init__
|
|
413
415
|
|
|
414
416
|
logger.info(
|
|
415
417
|
f"Temporal workflow proxy decorator applied to {cls.__name__}",
|
|
@@ -10,8 +10,6 @@ import asyncio
|
|
|
10
10
|
import inspect
|
|
11
11
|
from typing import (
|
|
12
12
|
Any,
|
|
13
|
-
List,
|
|
14
|
-
Optional,
|
|
15
13
|
Protocol,
|
|
16
14
|
TypeVar,
|
|
17
15
|
get_args,
|
|
@@ -62,7 +60,7 @@ class MockRepositoryProtocol(MockBaseRepositoryProtocol, Protocol):
|
|
|
62
60
|
"""Mock payment processing method."""
|
|
63
61
|
...
|
|
64
62
|
|
|
65
|
-
async def get_payment(self, payment_id: str) ->
|
|
63
|
+
async def get_payment(self, payment_id: str) -> dict | None:
|
|
66
64
|
"""Mock get payment method."""
|
|
67
65
|
...
|
|
68
66
|
|
|
@@ -98,7 +96,7 @@ class MockRepository(MockRepositoryProtocol):
|
|
|
98
96
|
"""Mock payment processing method."""
|
|
99
97
|
return {"status": "success", "order_id": order_id, "amount": amount}
|
|
100
98
|
|
|
101
|
-
async def get_payment(self, payment_id: str) ->
|
|
99
|
+
async def get_payment(self, payment_id: str) -> dict | None:
|
|
102
100
|
"""Mock get payment method."""
|
|
103
101
|
if payment_id == "not_found":
|
|
104
102
|
return None
|
|
@@ -239,7 +237,7 @@ def test_activity_names_with_different_prefixes() -> None:
|
|
|
239
237
|
captured_activity_names = []
|
|
240
238
|
original_activity_defn = activity.defn
|
|
241
239
|
|
|
242
|
-
def mock_activity_defn(name:
|
|
240
|
+
def mock_activity_defn(name: str | None = None, **kwargs: Any) -> Any:
|
|
243
241
|
"""Mock activity.defn to capture the activity names being created."""
|
|
244
242
|
if name:
|
|
245
243
|
captured_activity_names.append(name)
|
|
@@ -317,7 +315,7 @@ def test_decorator_handles_inheritance_correctly() -> None:
|
|
|
317
315
|
"amount": amount,
|
|
318
316
|
}
|
|
319
317
|
|
|
320
|
-
async def get_payment(self, payment_id: str) ->
|
|
318
|
+
async def get_payment(self, payment_id: str) -> dict | None:
|
|
321
319
|
if payment_id == "not_found":
|
|
322
320
|
return None
|
|
323
321
|
return {"payment_id": payment_id, "status": "completed"}
|
|
@@ -467,7 +465,7 @@ class MockDocumentRepository(BaseRepository[MockDocument], Protocol):
|
|
|
467
465
|
class NonGenericRepository(Protocol):
|
|
468
466
|
"""Repository that doesn't follow BaseRepository[T] pattern."""
|
|
469
467
|
|
|
470
|
-
async def get(self, id: str) ->
|
|
468
|
+
async def get(self, id: str) -> MockDocument | None: ...
|
|
471
469
|
|
|
472
470
|
|
|
473
471
|
class TestTypeExtraction:
|
|
@@ -521,7 +519,7 @@ class TestTypeSubstitution:
|
|
|
521
519
|
|
|
522
520
|
def test_substitutes_optional_typevar(self) -> None:
|
|
523
521
|
"""Test Optional[TypeVar] substitution."""
|
|
524
|
-
optional_t =
|
|
522
|
+
optional_t = T | None
|
|
525
523
|
result = _substitute_typevar_with_concrete(
|
|
526
524
|
optional_t, MockAssemblySpecification
|
|
527
525
|
)
|
|
@@ -535,7 +533,7 @@ class TestTypeSubstitution:
|
|
|
535
533
|
|
|
536
534
|
def test_substitutes_nested_generics(self) -> None:
|
|
537
535
|
"""Test substitution in nested generic types."""
|
|
538
|
-
nested_generic =
|
|
536
|
+
nested_generic = list[T | None]
|
|
539
537
|
result = _substitute_typevar_with_concrete(nested_generic, MockDocument)
|
|
540
538
|
|
|
541
539
|
# Should be List[Optional[MockDocument]]
|
|
@@ -621,20 +619,20 @@ class TestPydanticValidationDetection:
|
|
|
621
619
|
|
|
622
620
|
def test_detects_optional_pydantic_types(self) -> None:
|
|
623
621
|
"""Test detection of Optional[PydanticModel] types."""
|
|
624
|
-
assert _needs_pydantic_validation(
|
|
625
|
-
assert _needs_pydantic_validation(
|
|
622
|
+
assert _needs_pydantic_validation(MockAssemblySpecification | None)
|
|
623
|
+
assert _needs_pydantic_validation(MockDocument | None)
|
|
626
624
|
|
|
627
625
|
def test_rejects_non_pydantic_types(self) -> None:
|
|
628
626
|
"""Test that non-Pydantic types are not flagged for validation."""
|
|
629
627
|
assert not _needs_pydantic_validation(str)
|
|
630
628
|
assert not _needs_pydantic_validation(int)
|
|
631
629
|
assert not _needs_pydantic_validation(dict)
|
|
632
|
-
assert not _needs_pydantic_validation(
|
|
630
|
+
assert not _needs_pydantic_validation(str | None)
|
|
633
631
|
|
|
634
632
|
def test_rejects_typevar_types(self) -> None:
|
|
635
633
|
"""Test TypeVar types aren't flagged for validation (the bug)."""
|
|
636
634
|
assert not _needs_pydantic_validation(T)
|
|
637
|
-
assert not _needs_pydantic_validation(
|
|
635
|
+
assert not _needs_pydantic_validation(T | None)
|
|
638
636
|
|
|
639
637
|
def test_handles_none_and_empty(self) -> None:
|
|
640
638
|
"""Test handling of None and Signature.empty."""
|
|
@@ -733,7 +731,7 @@ class TestEndToEndTypeSubstitution:
|
|
|
733
731
|
def test_type_substitution_enables_pydantic_validation(self) -> None:
|
|
734
732
|
"""Test type substitution enables Pydantic validation."""
|
|
735
733
|
# Simulate the problematic method signature: Optional[~T]
|
|
736
|
-
original_annotation =
|
|
734
|
+
original_annotation = T | None
|
|
737
735
|
|
|
738
736
|
# Before fix: TypeVar prevents validation
|
|
739
737
|
assert not _needs_pydantic_validation(original_annotation)
|
|
@@ -760,7 +758,7 @@ class TestEndToEndTypeSubstitution:
|
|
|
760
758
|
assert isinstance(activity_result_dict, dict)
|
|
761
759
|
with pytest.raises(AttributeError):
|
|
762
760
|
# This would fail because dict doesn't have the attribute
|
|
763
|
-
|
|
761
|
+
_ = activity_result_dict.assembly_specification_id
|
|
764
762
|
|
|
765
763
|
# Demonstrate the solution: reconstruct Pydantic object
|
|
766
764
|
reconstructed = MockAssemblySpecification.model_validate(activity_result_dict)
|
|
@@ -6,7 +6,7 @@ their defined Protocols using @runtime_checkable.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import logging
|
|
9
|
-
from typing import
|
|
9
|
+
from typing import TypeVar
|
|
10
10
|
|
|
11
11
|
logger = logging.getLogger(__name__)
|
|
12
12
|
|
|
@@ -19,7 +19,7 @@ class RepositoryValidationError(Exception):
|
|
|
19
19
|
pass
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
def validate_repository_protocol(repository: object, protocol:
|
|
22
|
+
def validate_repository_protocol(repository: object, protocol: type[P]) -> None:
|
|
23
23
|
"""
|
|
24
24
|
Validate that a repository implementation satisfies a protocol contract.
|
|
25
25
|
|
|
@@ -72,7 +72,7 @@ def validate_repository_protocol(repository: object, protocol: Type[P]) -> None:
|
|
|
72
72
|
)
|
|
73
73
|
|
|
74
74
|
|
|
75
|
-
def ensure_repository_protocol(repository: object, protocol:
|
|
75
|
+
def ensure_repository_protocol(repository: object, protocol: type[P]) -> P:
|
|
76
76
|
"""
|
|
77
77
|
Validate and return a repository with proper type annotation.
|
|
78
78
|
|