julee 0.1.1__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.
Files changed (157) hide show
  1. julee/api/app.py +9 -8
  2. julee/api/dependencies.py +15 -15
  3. julee/api/requests.py +10 -9
  4. julee/api/responses.py +2 -1
  5. julee/api/routers/__init__.py +5 -5
  6. julee/api/routers/assembly_specifications.py +5 -4
  7. julee/api/routers/documents.py +1 -1
  8. julee/api/routers/knowledge_service_configs.py +4 -3
  9. julee/api/routers/knowledge_service_queries.py +7 -6
  10. julee/api/routers/system.py +4 -3
  11. julee/api/routers/workflows.py +4 -5
  12. julee/api/services/system_initialization.py +6 -6
  13. julee/api/tests/routers/test_assembly_specifications.py +4 -3
  14. julee/api/tests/routers/test_documents.py +5 -4
  15. julee/api/tests/routers/test_knowledge_service_configs.py +7 -6
  16. julee/api/tests/routers/test_knowledge_service_queries.py +4 -3
  17. julee/api/tests/routers/test_system.py +5 -4
  18. julee/api/tests/routers/test_workflows.py +5 -4
  19. julee/api/tests/test_app.py +5 -4
  20. julee/api/tests/test_dependencies.py +3 -2
  21. julee/api/tests/test_requests.py +2 -1
  22. julee/contrib/__init__.py +15 -0
  23. julee/contrib/polling/__init__.py +47 -0
  24. julee/contrib/polling/domain/__init__.py +17 -0
  25. julee/contrib/polling/domain/models/__init__.py +13 -0
  26. julee/contrib/polling/domain/models/polling_config.py +39 -0
  27. julee/contrib/polling/domain/services/__init__.py +11 -0
  28. julee/contrib/polling/domain/services/poller.py +39 -0
  29. julee/contrib/polling/infrastructure/__init__.py +15 -0
  30. julee/contrib/polling/infrastructure/services/__init__.py +12 -0
  31. julee/contrib/polling/infrastructure/services/polling/__init__.py +12 -0
  32. julee/contrib/polling/infrastructure/services/polling/http/__init__.py +12 -0
  33. julee/contrib/polling/infrastructure/services/polling/http/http_poller_service.py +80 -0
  34. julee/contrib/polling/infrastructure/temporal/__init__.py +20 -0
  35. julee/contrib/polling/infrastructure/temporal/activities.py +42 -0
  36. julee/contrib/polling/infrastructure/temporal/activity_names.py +20 -0
  37. julee/contrib/polling/infrastructure/temporal/proxies.py +45 -0
  38. julee/contrib/polling/tests/__init__.py +6 -0
  39. julee/contrib/polling/tests/unit/__init__.py +6 -0
  40. julee/contrib/polling/tests/unit/infrastructure/__init__.py +7 -0
  41. julee/contrib/polling/tests/unit/infrastructure/services/__init__.py +6 -0
  42. julee/contrib/polling/tests/unit/infrastructure/services/polling/__init__.py +6 -0
  43. julee/contrib/polling/tests/unit/infrastructure/services/polling/http/__init__.py +7 -0
  44. julee/contrib/polling/tests/unit/infrastructure/services/polling/http/test_http_poller_service.py +163 -0
  45. julee/docs/__init__.py +5 -0
  46. julee/docs/sphinx_hcd/__init__.py +82 -0
  47. julee/docs/sphinx_hcd/accelerators.py +1078 -0
  48. julee/docs/sphinx_hcd/apps.py +499 -0
  49. julee/docs/sphinx_hcd/config.py +148 -0
  50. julee/docs/sphinx_hcd/epics.py +448 -0
  51. julee/docs/sphinx_hcd/integrations.py +306 -0
  52. julee/docs/sphinx_hcd/journeys.py +783 -0
  53. julee/docs/sphinx_hcd/personas.py +435 -0
  54. julee/docs/sphinx_hcd/stories.py +932 -0
  55. julee/docs/sphinx_hcd/utils.py +180 -0
  56. julee/domain/models/__init__.py +5 -6
  57. julee/domain/models/assembly/assembly.py +7 -7
  58. julee/domain/models/assembly/tests/factories.py +2 -1
  59. julee/domain/models/assembly/tests/test_assembly.py +16 -13
  60. julee/domain/models/assembly_specification/assembly_specification.py +11 -10
  61. julee/domain/models/assembly_specification/knowledge_service_query.py +14 -9
  62. julee/domain/models/assembly_specification/tests/factories.py +2 -1
  63. julee/domain/models/assembly_specification/tests/test_assembly_specification.py +9 -6
  64. julee/domain/models/assembly_specification/tests/test_knowledge_service_query.py +3 -1
  65. julee/domain/models/custom_fields/content_stream.py +3 -2
  66. julee/domain/models/custom_fields/tests/test_custom_fields.py +2 -1
  67. julee/domain/models/document/document.py +12 -10
  68. julee/domain/models/document/tests/factories.py +3 -2
  69. julee/domain/models/document/tests/test_document.py +6 -3
  70. julee/domain/models/knowledge_service_config/knowledge_service_config.py +4 -4
  71. julee/domain/models/policy/__init__.py +4 -4
  72. julee/domain/models/policy/document_policy_validation.py +17 -17
  73. julee/domain/models/policy/policy.py +12 -10
  74. julee/domain/models/policy/tests/factories.py +2 -1
  75. julee/domain/models/policy/tests/test_document_policy_validation.py +3 -1
  76. julee/domain/models/policy/tests/test_policy.py +2 -1
  77. julee/domain/repositories/__init__.py +3 -3
  78. julee/domain/repositories/assembly.py +3 -1
  79. julee/domain/repositories/assembly_specification.py +2 -0
  80. julee/domain/repositories/base.py +33 -16
  81. julee/domain/repositories/document.py +3 -1
  82. julee/domain/repositories/document_policy_validation.py +3 -1
  83. julee/domain/repositories/knowledge_service_config.py +2 -0
  84. julee/domain/repositories/knowledge_service_query.py +1 -0
  85. julee/domain/repositories/policy.py +3 -1
  86. julee/domain/use_cases/decorators.py +3 -2
  87. julee/domain/use_cases/extract_assemble_data.py +23 -13
  88. julee/domain/use_cases/initialize_system_data.py +13 -13
  89. julee/domain/use_cases/tests/test_extract_assemble_data.py +10 -10
  90. julee/domain/use_cases/tests/test_initialize_system_data.py +2 -2
  91. julee/domain/use_cases/tests/test_validate_document.py +11 -11
  92. julee/domain/use_cases/validate_document.py +26 -15
  93. julee/maintenance/__init__.py +1 -0
  94. julee/maintenance/release.py +188 -0
  95. julee/repositories/__init__.py +4 -1
  96. julee/repositories/memory/assembly.py +6 -5
  97. julee/repositories/memory/assembly_specification.py +9 -9
  98. julee/repositories/memory/base.py +12 -11
  99. julee/repositories/memory/document.py +8 -7
  100. julee/repositories/memory/document_policy_validation.py +7 -6
  101. julee/repositories/memory/knowledge_service_config.py +9 -7
  102. julee/repositories/memory/knowledge_service_query.py +9 -7
  103. julee/repositories/memory/policy.py +6 -5
  104. julee/repositories/memory/tests/test_document.py +6 -4
  105. julee/repositories/memory/tests/test_document_policy_validation.py +2 -1
  106. julee/repositories/memory/tests/test_policy.py +2 -1
  107. julee/repositories/minio/assembly.py +4 -4
  108. julee/repositories/minio/assembly_specification.py +6 -8
  109. julee/repositories/minio/client.py +22 -25
  110. julee/repositories/minio/document.py +11 -11
  111. julee/repositories/minio/document_policy_validation.py +5 -5
  112. julee/repositories/minio/knowledge_service_config.py +8 -8
  113. julee/repositories/minio/knowledge_service_query.py +6 -9
  114. julee/repositories/minio/policy.py +4 -4
  115. julee/repositories/minio/tests/fake_client.py +11 -9
  116. julee/repositories/minio/tests/test_assembly.py +3 -1
  117. julee/repositories/minio/tests/test_assembly_specification.py +2 -1
  118. julee/repositories/minio/tests/test_client_protocol.py +5 -5
  119. julee/repositories/minio/tests/test_document.py +7 -6
  120. julee/repositories/minio/tests/test_document_policy_validation.py +3 -1
  121. julee/repositories/minio/tests/test_knowledge_service_config.py +4 -2
  122. julee/repositories/minio/tests/test_knowledge_service_query.py +3 -2
  123. julee/repositories/minio/tests/test_policy.py +3 -1
  124. julee/repositories/temporal/activities.py +5 -5
  125. julee/repositories/temporal/proxies.py +5 -5
  126. julee/services/knowledge_service/__init__.py +1 -2
  127. julee/services/knowledge_service/anthropic/knowledge_service.py +8 -7
  128. julee/services/knowledge_service/anthropic/tests/test_knowledge_service.py +11 -10
  129. julee/services/knowledge_service/factory.py +8 -8
  130. julee/services/knowledge_service/knowledge_service.py +22 -18
  131. julee/services/knowledge_service/memory/knowledge_service.py +13 -12
  132. julee/services/knowledge_service/memory/test_knowledge_service.py +10 -7
  133. julee/services/knowledge_service/test_factory.py +11 -10
  134. julee/services/temporal/activities.py +10 -10
  135. julee/services/temporal/proxies.py +2 -2
  136. julee/util/domain.py +6 -6
  137. julee/util/repos/minio/file_storage.py +8 -9
  138. julee/util/repos/temporal/client_proxies/file_storage.py +3 -4
  139. julee/util/repos/temporal/data_converter.py +6 -6
  140. julee/util/repos/temporal/minio_file_storage.py +1 -1
  141. julee/util/repos/temporal/proxies/file_storage.py +2 -3
  142. julee/util/repositories.py +6 -7
  143. julee/util/temporal/activities.py +1 -1
  144. julee/util/temporal/decorators.py +28 -33
  145. julee/util/tests/test_decorators.py +13 -15
  146. julee/util/validation/repository.py +3 -3
  147. julee/util/validation/type_guards.py +12 -11
  148. julee/worker.py +9 -8
  149. julee/workflows/__init__.py +2 -2
  150. julee/workflows/extract_assemble.py +2 -1
  151. julee/workflows/validate_document.py +3 -2
  152. {julee-0.1.1.dist-info → julee-0.1.3.dist-info}/METADATA +4 -1
  153. julee-0.1.3.dist-info/RECORD +197 -0
  154. julee-0.1.1.dist-info/RECORD +0 -161
  155. {julee-0.1.1.dist-info → julee-0.1.3.dist-info}/WHEEL +0 -0
  156. {julee-0.1.1.dist-info → julee-0.1.3.dist-info}/licenses/LICENSE +0 -0
  157. {julee-0.1.1.dist-info → julee-0.1.3.dist-info}/top_level.txt +0 -0
@@ -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 class
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: Optional[str] = None
15
- content_type: Optional[str] = None
16
- size_bytes: Optional[int] = None
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: Dict[str, str] = Field(default_factory=dict)
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: Optional[str] = None,
24
- access_key: Optional[str] = None,
25
- secret_key: Optional[str] = None,
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: Optional[str] = None,
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: Optional[Minio] = None
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) -> Optional[bytes]:
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) -> Optional[FileMetadata]:
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: Optional[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: Optional[FileStorageRepository] = None,
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) -> Optional[bytes]:
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) -> Optional[FileMetadata]:
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, Optional, Type
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: Optional[Type] = None,
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: Optional[ToJsonOptions] = None) -> None:
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: Optional[ToJsonOptions] = None,
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) -> Optional[bytes]:
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) -> Optional[FileMetadata]:
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(
@@ -1,4 +1,5 @@
1
- from typing import Protocol, Optional, runtime_checkable
1
+ from typing import Protocol, runtime_checkable
2
+
2
3
  from julee.util.domain import FileMetadata, FileUploadArgs
3
4
 
4
5
 
@@ -23,16 +24,14 @@ class FileStorageRepository(Protocol):
23
24
  FileMetadata object with details about the uploaded file.
24
25
 
25
26
  Implementation Notes:
26
- - Must be idempotent: uploading the same file_id multiple times is
27
- safe.
27
+ - Must be idempotent: uploading the same file_id multiple times is safe.
28
28
  - Should return metadata including the actual size and content type.
29
- - Must perform security validation: file size limits, content type
30
- verification, and filename sanitization.
29
+ - Must perform security validation: file size limits, content type verification, and filename sanitization.
31
30
  - Should reject files that don't match declared content type.
32
31
  """
33
32
  ...
34
33
 
35
- async def download_file(self, file_id: str) -> Optional[bytes]:
34
+ async def download_file(self, file_id: str) -> bytes | None:
36
35
  """Download a file from storage by its ID.
37
36
 
38
37
  Args:
@@ -43,7 +42,7 @@ class FileStorageRepository(Protocol):
43
42
  """
44
43
  ...
45
44
 
46
- async def get_file_metadata(self, file_id: str) -> Optional[FileMetadata]:
45
+ async def get_file_metadata(self, file_id: str) -> FileMetadata | None:
47
46
  """Retrieve metadata for a stored file.
48
47
 
49
48
  Args:
@@ -79,7 +79,7 @@ def collect_activities_from_instances(*instances: Any) -> list[Any]:
79
79
 
80
80
  Args:
81
81
  *instances: Repository and service instances decorated with
82
- @temporal_activity_registration
82
+ @temporal_activity_registration
83
83
 
84
84
  Returns:
85
85
  List of activity methods ready for Worker registration
@@ -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) -> Optional[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[[Type[T]], Type[T]]:
114
+ ) -> Callable[[type[T]], type[T]]:
117
115
  """
118
116
  Class decorator that wraps async protocol methods as Temporal activities.
119
117
 
@@ -123,13 +121,10 @@ def temporal_activity_registration(
123
121
  name.
124
122
 
125
123
  Args:
126
- activity_prefix: Prefix for activity names (e.g.,
127
- "sample.payment_repo.minio") Method names will be appended to create
128
- full activity names like "sample.payment_repo.minio.process_payment"
124
+ activity_prefix: Prefix for activity names (e.g., "sample.payment_repo.minio"). Method names will be appended to create full activity names like "sample.payment_repo.minio.process_payment"
129
125
 
130
126
  Returns:
131
- The decorated class with all async methods wrapped as Temporal
132
- activities
127
+ The decorated class with all async methods wrapped as Temporal activities
133
128
 
134
129
  Example:
135
130
  @temporal_activity_registration("sample.payment_repo.minio")
@@ -137,15 +132,12 @@ def temporal_activity_registration(
137
132
  pass
138
133
 
139
134
  # This automatically creates activities for all protocol methods:
140
- # - process_payment ->
141
- # "sample.payment_repo.minio.process_payment"
142
- # - get_payment ->
143
- # "sample.payment_repo.minio.get_payment"
144
- # - refund_payment ->
145
- # "sample.payment_repo.minio.refund_payment"
135
+ # - process_payment -> "sample.payment_repo.minio.process_payment"
136
+ # - get_payment -> "sample.payment_repo.minio.get_payment"
137
+ # - refund_payment -> "sample.payment_repo.minio.refund_payment"
146
138
  """
147
139
 
148
- def decorator(cls: Type[T]) -> Type[T]:
140
+ def decorator(cls: type[T]) -> type[T]:
149
141
  logger.debug(
150
142
  f"Applying temporal_activity_registration decorator to {cls.__name__}"
151
143
  )
@@ -186,8 +178,9 @@ def temporal_activity_registration(
186
178
  return wrapper_method
187
179
 
188
180
  # Create the wrapper and apply activity decorator
189
- wrapper_method = create_wrapper_method(method, name)
190
- wrapped_method = activity.defn(name=activity_name)(wrapper_method)
181
+ wrapped_method = activity.defn(name=activity_name)(
182
+ create_wrapper_method(method, name)
183
+ )
191
184
 
192
185
  # Replace the method on the class with the wrapped version
193
186
  setattr(cls, name, wrapped_method)
@@ -210,8 +203,8 @@ def temporal_activity_registration(
210
203
  def temporal_workflow_proxy(
211
204
  activity_base: str,
212
205
  default_timeout_seconds: int = 30,
213
- retry_methods: Optional[list[str]] = None,
214
- ) -> Callable[[Type[T]], Type[T]]:
206
+ retry_methods: list[str] | None = None,
207
+ ) -> Callable[[type[T]], type[T]]:
215
208
  """
216
209
  Class decorator that automatically creates workflow proxy methods that
217
210
  delegate to Temporal activities.
@@ -226,8 +219,8 @@ def temporal_workflow_proxy(
226
219
  retry_methods: List of method names that should use retry policies
227
220
 
228
221
  Returns:
229
- The decorated class with all protocol methods implemented as
230
- workflow activity calls
222
+ The decorated class with all protocol methods implemented as workflow
223
+ activity calls
231
224
 
232
225
  Example:
233
226
  @temporal_workflow_proxy(
@@ -241,11 +234,10 @@ def temporal_workflow_proxy(
241
234
  # This automatically creates workflow methods for all methods:
242
235
  # - get() -> calls "julee.document_repo.minio.get" activity
243
236
  # - save() -> calls "julee.document_repo.minio.save" with retry
244
- # - generate_id() -> calls "julee.document_repo.minio.generate_id"
245
- # with retry
237
+ # - generate_id() -> calls "julee.document_repo.minio.generate_id" with retry
246
238
  """
247
239
 
248
- def decorator(cls: Type[T]) -> Type[T]:
240
+ def decorator(cls: type[T]) -> type[T]:
249
241
  logger.debug(f"Applying temporal_workflow_proxy decorator to {cls.__name__}")
250
242
 
251
243
  retry_methods_set = set(retry_methods or [])
@@ -397,14 +389,17 @@ def temporal_workflow_proxy(
397
389
  return workflow_method
398
390
 
399
391
  # Create and set the method on the class
400
- workflow_method = create_workflow_method(
392
+ setattr(
393
+ cls,
401
394
  method_name,
402
- needs_validation,
403
- is_optional,
404
- inner_type,
405
- original_method,
395
+ create_workflow_method(
396
+ method_name,
397
+ needs_validation,
398
+ is_optional,
399
+ inner_type,
400
+ original_method,
401
+ ),
406
402
  )
407
- setattr(cls, method_name, workflow_method)
408
403
  wrapped_methods.append(method_name)
409
404
 
410
405
  # Always generate __init__ that calls super() for consistent init
@@ -416,7 +411,7 @@ def temporal_workflow_proxy(
416
411
  proxy_self.activity_fail_fast_retry_policy = fail_fast_retry_policy
417
412
  logger.debug(f"Initialized {cls.__name__}")
418
413
 
419
- setattr(cls, "__init__", __init__)
414
+ cls.__init__ = __init__
420
415
 
421
416
  logger.info(
422
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) -> Optional[dict]:
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) -> Optional[dict]:
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: Optional[str] = None, **kwargs: Any) -> Any:
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) -> Optional[dict]:
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) -> Optional[MockDocument]: ...
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 = 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 = List[Optional[T]]
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(Optional[MockAssemblySpecification])
625
- assert _needs_pydantic_validation(Optional[MockDocument])
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(Optional[str])
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(Optional[T])
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 = Optional[T]
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
- getattr(activity_result_dict, "assembly_specification_id")
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 Type, TypeVar
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: Type[P]) -> None:
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: Type[P]) -> P:
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
 
@@ -11,17 +11,16 @@ providing clear error messages when types don't match expectations.
11
11
 
12
12
  import inspect
13
13
  import logging
14
+ from collections.abc import Callable
14
15
  from functools import wraps
15
16
  from typing import (
16
17
  Any,
17
- Dict,
18
- List,
19
18
  Union,
20
- Callable,
21
- get_type_hints,
22
- get_origin,
23
19
  get_args,
20
+ get_origin,
21
+ get_type_hints,
24
22
  )
23
+
25
24
  from pydantic import BaseModel
26
25
 
27
26
  logger = logging.getLogger(__name__)
@@ -54,7 +53,7 @@ def validate_type(
54
53
  if allow_none:
55
54
  return
56
55
  raise TypeValidationError(
57
- f"{context_name}: Expected {_format_type(expected_type)}, " f"got None"
56
+ f"{context_name}: Expected {_format_type(expected_type)}, got None"
58
57
  )
59
58
 
60
59
  # Get the origin type for generic types (List[X] -> list, Dict -> dict)
@@ -124,7 +123,7 @@ def _validate_generic_type(
124
123
 
125
124
 
126
125
  def _validate_list_contents(
127
- value: List[Any], type_args: tuple, context_name: str
126
+ value: list[Any], type_args: tuple, context_name: str
128
127
  ) -> None:
129
128
  """Validate contents of a list."""
130
129
  if not type_args:
@@ -143,7 +142,7 @@ def _validate_list_contents(
143
142
 
144
143
 
145
144
  def _validate_dict_contents(
146
- value: Dict[Any, Any], type_args: tuple, context_name: str
145
+ value: dict[Any, Any], type_args: tuple, context_name: str
147
146
  ) -> None:
148
147
  """Validate contents of a dictionary."""
149
148
  if len(type_args) < 2:
@@ -241,7 +240,7 @@ def _raise_pydantic_dict_error(
241
240
  "dict_keys": dict_keys,
242
241
  "model_fields": model_fields,
243
242
  "matching_fields": matching_fields,
244
- "dict_sample": {k: v for k, v in list(value.items())[:3]},
243
+ "dict_sample": dict(list(value.items())[:3]),
245
244
  },
246
245
  )
247
246
 
@@ -263,7 +262,7 @@ def _format_value(value: Any, max_items: int = 5) -> str:
263
262
  else:
264
263
  items = list(value.items())[:max_items]
265
264
  return f"{dict(items)}... ({len(value)} total items)"
266
- elif isinstance(value, (list, tuple)):
265
+ elif isinstance(value, list | tuple):
267
266
  if len(value) <= max_items:
268
267
  return str(value)
269
268
  else:
@@ -308,7 +307,9 @@ def validate_parameter_types(
308
307
  param_names = list(sig.parameters.keys())
309
308
 
310
309
  # Validate positional arguments
311
- for i, (param_name, arg_value) in enumerate(zip(param_names, args)):
310
+ for i, (param_name, arg_value) in enumerate(
311
+ zip(param_names, args, strict=False)
312
+ ):
312
313
  if param_name in types_to_check and param_name != "self":
313
314
  try:
314
315
  validate_type(