julee 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. julee/__init__.py +3 -0
  2. julee/api/__init__.py +20 -0
  3. julee/api/app.py +180 -0
  4. julee/api/dependencies.py +257 -0
  5. julee/api/requests.py +175 -0
  6. julee/api/responses.py +43 -0
  7. julee/api/routers/__init__.py +43 -0
  8. julee/api/routers/assembly_specifications.py +212 -0
  9. julee/api/routers/documents.py +182 -0
  10. julee/api/routers/knowledge_service_configs.py +79 -0
  11. julee/api/routers/knowledge_service_queries.py +293 -0
  12. julee/api/routers/system.py +137 -0
  13. julee/api/routers/workflows.py +234 -0
  14. julee/api/services/__init__.py +20 -0
  15. julee/api/services/system_initialization.py +214 -0
  16. julee/api/tests/__init__.py +14 -0
  17. julee/api/tests/routers/__init__.py +17 -0
  18. julee/api/tests/routers/test_assembly_specifications.py +749 -0
  19. julee/api/tests/routers/test_documents.py +301 -0
  20. julee/api/tests/routers/test_knowledge_service_configs.py +234 -0
  21. julee/api/tests/routers/test_knowledge_service_queries.py +738 -0
  22. julee/api/tests/routers/test_system.py +179 -0
  23. julee/api/tests/routers/test_workflows.py +393 -0
  24. julee/api/tests/test_app.py +285 -0
  25. julee/api/tests/test_dependencies.py +245 -0
  26. julee/api/tests/test_requests.py +250 -0
  27. julee/domain/__init__.py +22 -0
  28. julee/domain/models/__init__.py +49 -0
  29. julee/domain/models/assembly/__init__.py +17 -0
  30. julee/domain/models/assembly/assembly.py +103 -0
  31. julee/domain/models/assembly/tests/__init__.py +0 -0
  32. julee/domain/models/assembly/tests/factories.py +37 -0
  33. julee/domain/models/assembly/tests/test_assembly.py +430 -0
  34. julee/domain/models/assembly_specification/__init__.py +24 -0
  35. julee/domain/models/assembly_specification/assembly_specification.py +172 -0
  36. julee/domain/models/assembly_specification/knowledge_service_query.py +123 -0
  37. julee/domain/models/assembly_specification/tests/__init__.py +0 -0
  38. julee/domain/models/assembly_specification/tests/factories.py +78 -0
  39. julee/domain/models/assembly_specification/tests/test_assembly_specification.py +490 -0
  40. julee/domain/models/assembly_specification/tests/test_knowledge_service_query.py +310 -0
  41. julee/domain/models/custom_fields/__init__.py +0 -0
  42. julee/domain/models/custom_fields/content_stream.py +68 -0
  43. julee/domain/models/custom_fields/tests/__init__.py +0 -0
  44. julee/domain/models/custom_fields/tests/test_custom_fields.py +53 -0
  45. julee/domain/models/document/__init__.py +17 -0
  46. julee/domain/models/document/document.py +150 -0
  47. julee/domain/models/document/tests/__init__.py +0 -0
  48. julee/domain/models/document/tests/factories.py +76 -0
  49. julee/domain/models/document/tests/test_document.py +297 -0
  50. julee/domain/models/knowledge_service_config/__init__.py +17 -0
  51. julee/domain/models/knowledge_service_config/knowledge_service_config.py +86 -0
  52. julee/domain/models/policy/__init__.py +15 -0
  53. julee/domain/models/policy/document_policy_validation.py +220 -0
  54. julee/domain/models/policy/policy.py +203 -0
  55. julee/domain/models/policy/tests/__init__.py +0 -0
  56. julee/domain/models/policy/tests/factories.py +47 -0
  57. julee/domain/models/policy/tests/test_document_policy_validation.py +420 -0
  58. julee/domain/models/policy/tests/test_policy.py +546 -0
  59. julee/domain/repositories/__init__.py +27 -0
  60. julee/domain/repositories/assembly.py +45 -0
  61. julee/domain/repositories/assembly_specification.py +52 -0
  62. julee/domain/repositories/base.py +146 -0
  63. julee/domain/repositories/document.py +49 -0
  64. julee/domain/repositories/document_policy_validation.py +52 -0
  65. julee/domain/repositories/knowledge_service_config.py +54 -0
  66. julee/domain/repositories/knowledge_service_query.py +44 -0
  67. julee/domain/repositories/policy.py +49 -0
  68. julee/domain/use_cases/__init__.py +17 -0
  69. julee/domain/use_cases/decorators.py +107 -0
  70. julee/domain/use_cases/extract_assemble_data.py +649 -0
  71. julee/domain/use_cases/initialize_system_data.py +842 -0
  72. julee/domain/use_cases/tests/__init__.py +7 -0
  73. julee/domain/use_cases/tests/test_extract_assemble_data.py +548 -0
  74. julee/domain/use_cases/tests/test_initialize_system_data.py +455 -0
  75. julee/domain/use_cases/tests/test_validate_document.py +1228 -0
  76. julee/domain/use_cases/validate_document.py +736 -0
  77. julee/fixtures/assembly_specifications.yaml +70 -0
  78. julee/fixtures/documents.yaml +178 -0
  79. julee/fixtures/knowledge_service_configs.yaml +37 -0
  80. julee/fixtures/knowledge_service_queries.yaml +27 -0
  81. julee/repositories/__init__.py +17 -0
  82. julee/repositories/memory/__init__.py +31 -0
  83. julee/repositories/memory/assembly.py +84 -0
  84. julee/repositories/memory/assembly_specification.py +125 -0
  85. julee/repositories/memory/base.py +227 -0
  86. julee/repositories/memory/document.py +149 -0
  87. julee/repositories/memory/document_policy_validation.py +104 -0
  88. julee/repositories/memory/knowledge_service_config.py +123 -0
  89. julee/repositories/memory/knowledge_service_query.py +120 -0
  90. julee/repositories/memory/policy.py +87 -0
  91. julee/repositories/memory/tests/__init__.py +0 -0
  92. julee/repositories/memory/tests/test_document.py +212 -0
  93. julee/repositories/memory/tests/test_document_policy_validation.py +161 -0
  94. julee/repositories/memory/tests/test_policy.py +443 -0
  95. julee/repositories/minio/__init__.py +31 -0
  96. julee/repositories/minio/assembly.py +103 -0
  97. julee/repositories/minio/assembly_specification.py +170 -0
  98. julee/repositories/minio/client.py +570 -0
  99. julee/repositories/minio/document.py +530 -0
  100. julee/repositories/minio/document_policy_validation.py +120 -0
  101. julee/repositories/minio/knowledge_service_config.py +187 -0
  102. julee/repositories/minio/knowledge_service_query.py +211 -0
  103. julee/repositories/minio/policy.py +106 -0
  104. julee/repositories/minio/tests/__init__.py +0 -0
  105. julee/repositories/minio/tests/fake_client.py +213 -0
  106. julee/repositories/minio/tests/test_assembly.py +374 -0
  107. julee/repositories/minio/tests/test_assembly_specification.py +391 -0
  108. julee/repositories/minio/tests/test_client_protocol.py +57 -0
  109. julee/repositories/minio/tests/test_document.py +591 -0
  110. julee/repositories/minio/tests/test_document_policy_validation.py +192 -0
  111. julee/repositories/minio/tests/test_knowledge_service_config.py +374 -0
  112. julee/repositories/minio/tests/test_knowledge_service_query.py +438 -0
  113. julee/repositories/minio/tests/test_policy.py +559 -0
  114. julee/repositories/temporal/__init__.py +38 -0
  115. julee/repositories/temporal/activities.py +114 -0
  116. julee/repositories/temporal/activity_names.py +34 -0
  117. julee/repositories/temporal/proxies.py +159 -0
  118. julee/services/__init__.py +18 -0
  119. julee/services/knowledge_service/__init__.py +48 -0
  120. julee/services/knowledge_service/anthropic/__init__.py +12 -0
  121. julee/services/knowledge_service/anthropic/knowledge_service.py +331 -0
  122. julee/services/knowledge_service/anthropic/tests/test_knowledge_service.py +318 -0
  123. julee/services/knowledge_service/factory.py +138 -0
  124. julee/services/knowledge_service/knowledge_service.py +160 -0
  125. julee/services/knowledge_service/memory/__init__.py +13 -0
  126. julee/services/knowledge_service/memory/knowledge_service.py +278 -0
  127. julee/services/knowledge_service/memory/test_knowledge_service.py +345 -0
  128. julee/services/knowledge_service/test_factory.py +112 -0
  129. julee/services/temporal/__init__.py +38 -0
  130. julee/services/temporal/activities.py +86 -0
  131. julee/services/temporal/activity_names.py +22 -0
  132. julee/services/temporal/proxies.py +41 -0
  133. julee/util/__init__.py +0 -0
  134. julee/util/domain.py +119 -0
  135. julee/util/repos/__init__.py +0 -0
  136. julee/util/repos/minio/__init__.py +0 -0
  137. julee/util/repos/minio/file_storage.py +213 -0
  138. julee/util/repos/temporal/__init__.py +11 -0
  139. julee/util/repos/temporal/client_proxies/file_storage.py +68 -0
  140. julee/util/repos/temporal/data_converter.py +123 -0
  141. julee/util/repos/temporal/minio_file_storage.py +12 -0
  142. julee/util/repos/temporal/proxies/__init__.py +0 -0
  143. julee/util/repos/temporal/proxies/file_storage.py +58 -0
  144. julee/util/repositories.py +55 -0
  145. julee/util/temporal/__init__.py +22 -0
  146. julee/util/temporal/activities.py +123 -0
  147. julee/util/temporal/decorators.py +473 -0
  148. julee/util/tests/__init__.py +1 -0
  149. julee/util/tests/test_decorators.py +770 -0
  150. julee/util/validation/__init__.py +29 -0
  151. julee/util/validation/repository.py +100 -0
  152. julee/util/validation/type_guards.py +369 -0
  153. julee/worker.py +211 -0
  154. julee/workflows/__init__.py +26 -0
  155. julee/workflows/extract_assemble.py +215 -0
  156. julee/workflows/validate_document.py +228 -0
  157. julee-0.1.0.dist-info/METADATA +195 -0
  158. julee-0.1.0.dist-info/RECORD +161 -0
  159. julee-0.1.0.dist-info/WHEEL +5 -0
  160. julee-0.1.0.dist-info/licenses/LICENSE +674 -0
  161. julee-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,29 @@
1
+ """
2
+ Validation utilities for type checking and debugging serialization issues.
3
+
4
+ This module provides utilities for validating runtime types against expected
5
+ types, with special focus on debugging common serialization issues in
6
+ Temporal workflows where Pydantic models get deserialized as dictionaries.
7
+ """
8
+
9
+ from .repository import (
10
+ RepositoryValidationError,
11
+ ensure_repository_protocol,
12
+ validate_repository_protocol,
13
+ )
14
+ from .type_guards import (
15
+ TypeValidationError,
16
+ guard_check,
17
+ validate_parameter_types,
18
+ validate_type,
19
+ )
20
+
21
+ __all__ = [
22
+ "TypeValidationError",
23
+ "validate_type",
24
+ "validate_parameter_types",
25
+ "guard_check",
26
+ "RepositoryValidationError",
27
+ "validate_repository_protocol",
28
+ "ensure_repository_protocol",
29
+ ]
@@ -0,0 +1,100 @@
1
+ """
2
+ Repository validation utilities for ensuring architectural contracts.
3
+
4
+ This module provides functions to validate repository implementations against
5
+ their defined Protocols using @runtime_checkable.
6
+ """
7
+
8
+ import logging
9
+ from typing import Type, TypeVar
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ P = TypeVar("P")
14
+
15
+
16
+ class RepositoryValidationError(Exception):
17
+ """Raised when repository contract validation fails"""
18
+
19
+ pass
20
+
21
+
22
+ def validate_repository_protocol(repository: object, protocol: Type[P]) -> None:
23
+ """
24
+ Validate that a repository implementation satisfies a protocol contract.
25
+
26
+ Uses Python's built-in isinstance() with @runtime_checkable for robust,
27
+ idiomatic protocol validation.
28
+
29
+ Args:
30
+ repository: The repository implementation to validate
31
+ protocol: The protocol class to validate against
32
+
33
+ Raises:
34
+ RepositoryValidationError: If validation fails
35
+
36
+ Example:
37
+ >>> from julee.util.validation.repository import validate_repository_protocol
38
+ >>> from julee.domain.repositories import DocumentRepository
39
+ >>> repo = MinioDocumentRepository()
40
+ >>> validate_repository_protocol(repo, DocumentRepository)
41
+ """
42
+ logger.debug(
43
+ "Validating repository protocol",
44
+ extra={
45
+ "repository_type": type(repository).__name__,
46
+ "protocol_name": protocol.__name__,
47
+ },
48
+ )
49
+
50
+ if not isinstance(repository, protocol):
51
+ error_message = (
52
+ f"Repository {type(repository).__name__} does not implement "
53
+ f"{protocol.__name__} protocol. Missing or incorrect methods."
54
+ )
55
+
56
+ logger.error(
57
+ "Repository protocol validation failed",
58
+ extra={
59
+ "repository_type": type(repository).__name__,
60
+ "protocol_name": protocol.__name__,
61
+ },
62
+ )
63
+
64
+ raise RepositoryValidationError(error_message)
65
+
66
+ logger.info(
67
+ "Repository protocol validation passed",
68
+ extra={
69
+ "repository_type": type(repository).__name__,
70
+ "protocol_name": protocol.__name__,
71
+ },
72
+ )
73
+
74
+
75
+ def ensure_repository_protocol(repository: object, protocol: Type[P]) -> P:
76
+ """
77
+ Validate and return a repository with proper type annotation.
78
+
79
+ This provides both runtime validation and static type checking benefits.
80
+
81
+ Args:
82
+ repository: The repository implementation to validate
83
+ protocol: The protocol class to validate against
84
+
85
+ Returns:
86
+ The validated repository (type checker knows it satisfies the
87
+ protocol)
88
+
89
+ Raises:
90
+ RepositoryValidationError: If validation fails
91
+
92
+ Example:
93
+ >>> from julee.util.validation.repository import ensure_repository_protocol
94
+ >>> from julee.domain.repositories import DocumentRepository
95
+ >>> repo = MinioDocumentRepository()
96
+ >>> validated_repo = ensure_repository_protocol(repo, DocumentRepository)
97
+ >>> # Type checker now knows validated_repo satisfies DocumentRepository
98
+ """
99
+ validate_repository_protocol(repository, protocol)
100
+ return repository # type: ignore[return-value]
@@ -0,0 +1,369 @@
1
+ """
2
+ Generic type guard validation system for debugging serialization issues.
3
+
4
+ This module provides utilities for validating that runtime values match their
5
+ expected types, with detailed diagnostics for common serialization issues
6
+ like Pydantic models being deserialized as dictionaries.
7
+
8
+ The system can work with any function by introspecting type hints and
9
+ providing clear error messages when types don't match expectations.
10
+ """
11
+
12
+ import inspect
13
+ import logging
14
+ from functools import wraps
15
+ from typing import (
16
+ Any,
17
+ Dict,
18
+ List,
19
+ Union,
20
+ Callable,
21
+ get_type_hints,
22
+ get_origin,
23
+ get_args,
24
+ )
25
+ from pydantic import BaseModel
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+
30
+ class TypeValidationError(TypeError):
31
+ """Raised when type validation fails with detailed diagnostics."""
32
+
33
+
34
+ def validate_type(
35
+ value: Any,
36
+ expected_type: Any,
37
+ context_name: str = "value",
38
+ allow_none: bool = False,
39
+ ) -> None:
40
+ """
41
+ Validate that a value matches the expected type with detailed diagnostics.
42
+
43
+ Args:
44
+ value: The actual value to validate
45
+ expected_type: The expected type (from type hints)
46
+ context_name: Name for error messages (e.g., "parameter 'queries'")
47
+ allow_none: Whether None values are acceptable
48
+
49
+ Raises:
50
+ TypeValidationError: With detailed diagnosis if type doesn't match
51
+ """
52
+ # Handle None values
53
+ if value is None:
54
+ if allow_none:
55
+ return
56
+ raise TypeValidationError(
57
+ f"{context_name}: Expected {_format_type(expected_type)}, " f"got None"
58
+ )
59
+
60
+ # Get the origin type for generic types (List[X] -> list, Dict -> dict)
61
+ origin_type = get_origin(expected_type)
62
+ type_args = get_args(expected_type)
63
+
64
+ # If no origin, it's a simple type
65
+ if origin_type is None:
66
+ _validate_simple_type(value, expected_type, context_name)
67
+ else:
68
+ _validate_generic_type(
69
+ value, expected_type, origin_type, type_args, context_name
70
+ )
71
+
72
+
73
+ def _validate_simple_type(value: Any, expected_type: Any, context_name: str) -> None:
74
+ """Validate simple (non-generic) types."""
75
+ actual_type = type(value)
76
+
77
+ # Check if it's the expected type
78
+ if isinstance(value, expected_type):
79
+ return
80
+
81
+ # Special handling for Pydantic models vs dicts (serialization issue)
82
+ if (
83
+ inspect.isclass(expected_type)
84
+ and issubclass(expected_type, BaseModel)
85
+ and isinstance(value, dict)
86
+ ):
87
+ _raise_pydantic_dict_error(value, expected_type, context_name)
88
+
89
+ # Generic type mismatch
90
+ raise TypeValidationError(
91
+ f"{context_name}: Type mismatch\n"
92
+ f" Expected: {_format_type(expected_type)}\n"
93
+ f" Actual: {actual_type.__name__}\n"
94
+ f" Value: {_format_value(value)}"
95
+ )
96
+
97
+
98
+ def _validate_generic_type(
99
+ value: Any,
100
+ expected_type: Any,
101
+ origin_type: Any,
102
+ type_args: tuple,
103
+ context_name: str,
104
+ ) -> None:
105
+ """Validate generic types like List[X], Dict[K,V], etc."""
106
+
107
+ # Check the container type first
108
+ if not isinstance(value, origin_type):
109
+ raise TypeValidationError(
110
+ f"{context_name}: Container type mismatch\n"
111
+ f" Expected container: {origin_type.__name__}\n"
112
+ f" Actual container: {type(value).__name__}\n"
113
+ f" Expected full type: {_format_type(expected_type)}\n"
114
+ f" Value: {_format_value(value)}"
115
+ )
116
+
117
+ # Validate contents based on container type
118
+ if origin_type is list:
119
+ _validate_list_contents(value, type_args, context_name)
120
+ elif origin_type is dict:
121
+ _validate_dict_contents(value, type_args, context_name)
122
+ elif origin_type is Union:
123
+ _validate_union_type(value, type_args, context_name)
124
+
125
+
126
+ def _validate_list_contents(
127
+ value: List[Any], type_args: tuple, context_name: str
128
+ ) -> None:
129
+ """Validate contents of a list."""
130
+ if not type_args:
131
+ return # List without type args, can't validate contents
132
+
133
+ element_type = type_args[0]
134
+
135
+ for i, element in enumerate(value):
136
+ try:
137
+ validate_type(element, element_type, f"{context_name}[{i}]")
138
+ except TypeValidationError as e:
139
+ # Re-raise with additional context
140
+ raise TypeValidationError(
141
+ f"List element validation failed:\n{str(e)}"
142
+ ) from e
143
+
144
+
145
+ def _validate_dict_contents(
146
+ value: Dict[Any, Any], type_args: tuple, context_name: str
147
+ ) -> None:
148
+ """Validate contents of a dictionary."""
149
+ if len(type_args) < 2:
150
+ return # Dict without full type args, can't validate contents
151
+
152
+ key_type, value_type = type_args[0], type_args[1]
153
+
154
+ for key, val in value.items():
155
+ # Validate key type
156
+ try:
157
+ validate_type(key, key_type, f"{context_name} key '{key}'")
158
+ except TypeValidationError as e:
159
+ raise TypeValidationError(
160
+ f"Dictionary key validation failed:\n{str(e)}"
161
+ ) from e
162
+
163
+ # Validate value type
164
+ try:
165
+ validate_type(val, value_type, f"{context_name}['{key}']")
166
+ except TypeValidationError as e:
167
+ raise TypeValidationError(
168
+ f"Dictionary value validation failed:\n{str(e)}"
169
+ ) from e
170
+
171
+
172
+ def _validate_union_type(value: Any, type_args: tuple, context_name: str) -> None:
173
+ """Validate Union types (including Optional)."""
174
+ # Try each type in the union
175
+ for union_type in type_args:
176
+ try:
177
+ allow_none = union_type is type(None)
178
+ validate_type(value, union_type, context_name, allow_none=allow_none)
179
+ return # If any type matches, we're good
180
+ except TypeValidationError:
181
+ continue # Try the next type
182
+
183
+ # None of the union types matched
184
+ type_names = [_format_type(t) for t in type_args]
185
+ raise TypeValidationError(
186
+ f"{context_name}: Value doesn't match any type in Union\n"
187
+ f" Expected one of: {', '.join(type_names)}\n"
188
+ f" Actual: {type(value).__name__}\n"
189
+ f" Value: {_format_value(value)}"
190
+ )
191
+
192
+
193
+ def _raise_pydantic_dict_error(
194
+ value: dict, expected_type: type, context_name: str
195
+ ) -> None:
196
+ """Raise a detailed error for Pydantic model vs dict issues."""
197
+ dict_keys = list(value.keys())
198
+
199
+ # Try to identify if this looks like a serialized Pydantic model
200
+ model_fields = []
201
+ if hasattr(expected_type, "model_fields"):
202
+ model_fields = list(expected_type.model_fields.keys())
203
+
204
+ matching_fields = [k for k in dict_keys if k in model_fields]
205
+
206
+ error_msg = (
207
+ f"SERIALIZATION ISSUE DETECTED: {context_name} is dict instead of "
208
+ f"{expected_type.__name__}!\n"
209
+ f" Expected Type: {expected_type.__name__}\n"
210
+ f" Expected Module: {expected_type.__module__}\n"
211
+ f" Actual Type: dict\n"
212
+ f" Dict Keys: {dict_keys}\n"
213
+ )
214
+
215
+ if model_fields:
216
+ error_msg += (
217
+ f" Expected Model Fields: {model_fields}\n"
218
+ f" Matching Fields: {matching_fields}\n"
219
+ f" Missing Fields: "
220
+ f"{[f for f in model_fields if f not in dict_keys]}\n"
221
+ f" Extra Fields: "
222
+ f"{[k for k in dict_keys if k not in model_fields]}\n"
223
+ )
224
+
225
+ error_msg += (
226
+ f" Sample Dict Content: {_format_value(value, max_items=3)}\n"
227
+ f" This indicates a Temporal/serialization issue where a Pydantic "
228
+ f"model\n"
229
+ f" was serialized correctly but deserialized as a plain "
230
+ f"dictionary.\n"
231
+ f" Check your data converter configuration and type hints."
232
+ )
233
+
234
+ logger.error(
235
+ "Pydantic serialization issue detected",
236
+ extra={
237
+ "context_name": context_name,
238
+ "expected_type": expected_type.__name__,
239
+ "expected_module": expected_type.__module__,
240
+ "actual_type": "dict",
241
+ "dict_keys": dict_keys,
242
+ "model_fields": model_fields,
243
+ "matching_fields": matching_fields,
244
+ "dict_sample": {k: v for k, v in list(value.items())[:3]},
245
+ },
246
+ )
247
+
248
+ raise TypeValidationError(error_msg)
249
+
250
+
251
+ def _format_type(type_hint: Any) -> str:
252
+ """Format a type hint for display in error messages."""
253
+ if hasattr(type_hint, "__name__"):
254
+ return str(type_hint.__name__)
255
+ return str(type_hint)
256
+
257
+
258
+ def _format_value(value: Any, max_items: int = 5) -> str:
259
+ """Format a value for display in error messages."""
260
+ if isinstance(value, dict):
261
+ if len(value) <= max_items:
262
+ return str(value)
263
+ else:
264
+ items = list(value.items())[:max_items]
265
+ return f"{dict(items)}... ({len(value)} total items)"
266
+ elif isinstance(value, (list, tuple)):
267
+ if len(value) <= max_items:
268
+ return str(value)
269
+ else:
270
+ return f"{value[:max_items]}... ({len(value)} total items)"
271
+ else:
272
+ return repr(value)
273
+
274
+
275
+ def validate_parameter_types(
276
+ **expected_types: Any,
277
+ ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
278
+ """
279
+ Decorator to validate function parameters against their expected types.
280
+
281
+ Usage:
282
+ @validate_parameter_types(queries=Dict[str, KnowledgeServiceQuery])
283
+ def my_function(self, queries):
284
+ # parameters are validated before function runs
285
+ pass
286
+
287
+ Or automatically from type hints:
288
+ @validate_parameter_types()
289
+ def my_function(self, queries: Dict[str, KnowledgeServiceQuery]):
290
+ # type hints are used automatically
291
+ pass
292
+ """
293
+
294
+ def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
295
+ @wraps(func)
296
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
297
+ # Get type hints if no explicit types provided
298
+ types_to_check = expected_types
299
+ if not types_to_check:
300
+ try:
301
+ types_to_check = get_type_hints(func)
302
+ except Exception as e:
303
+ logger.warning(f"Could not get type hints for {func.__name__}: {e}")
304
+ return func(*args, **kwargs)
305
+
306
+ # Get parameter names
307
+ sig = inspect.signature(func)
308
+ param_names = list(sig.parameters.keys())
309
+
310
+ # Validate positional arguments
311
+ for i, (param_name, arg_value) in enumerate(zip(param_names, args)):
312
+ if param_name in types_to_check and param_name != "self":
313
+ try:
314
+ validate_type(
315
+ arg_value,
316
+ types_to_check[param_name],
317
+ f"parameter '{param_name}'",
318
+ )
319
+ except TypeValidationError:
320
+ logger.error(
321
+ f"Parameter validation failed in {func.__name__}",
322
+ extra={
323
+ "function": func.__name__,
324
+ "parameter": param_name,
325
+ "parameter_index": i,
326
+ },
327
+ )
328
+ raise
329
+
330
+ # Validate keyword arguments
331
+ for param_name, arg_value in kwargs.items():
332
+ if param_name in types_to_check:
333
+ try:
334
+ validate_type(
335
+ arg_value,
336
+ types_to_check[param_name],
337
+ f"parameter '{param_name}'",
338
+ )
339
+ except TypeValidationError:
340
+ logger.error(
341
+ f"Parameter validation failed in {func.__name__}",
342
+ extra={
343
+ "function": func.__name__,
344
+ "parameter": param_name,
345
+ },
346
+ )
347
+ raise
348
+
349
+ return func(*args, **kwargs)
350
+
351
+ return wrapper
352
+
353
+ return decorator
354
+
355
+
356
+ def guard_check(value: Any, expected_type: Any, context_name: str = "value") -> None:
357
+ """
358
+ Simple guard check function for manual validation.
359
+
360
+ Usage:
361
+ guard_check(queries, Dict[str, KnowledgeServiceQuery], "queries")
362
+ guard_check(document, Document, "document")
363
+ """
364
+ try:
365
+ validate_type(value, expected_type, context_name)
366
+ logger.debug(f"Type validation passed for {context_name}")
367
+ except TypeValidationError:
368
+ logger.error(f"Guard check failed for {context_name}")
369
+ raise