julee 0.1.2__py3-none-any.whl → 0.1.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) 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 +11 -10
  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 +76 -0
  47. julee/docs/sphinx_hcd/accelerators.py +1175 -0
  48. julee/docs/sphinx_hcd/apps.py +518 -0
  49. julee/docs/sphinx_hcd/config.py +148 -0
  50. julee/docs/sphinx_hcd/epics.py +453 -0
  51. julee/docs/sphinx_hcd/integrations.py +310 -0
  52. julee/docs/sphinx_hcd/journeys.py +797 -0
  53. julee/docs/sphinx_hcd/personas.py +457 -0
  54. julee/docs/sphinx_hcd/stories.py +960 -0
  55. julee/docs/sphinx_hcd/utils.py +185 -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 +7 -6
  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 +23 -30
  68. julee/domain/models/document/tests/factories.py +3 -2
  69. julee/domain/models/document/tests/test_document.py +20 -37
  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 +10 -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 +5 -4
  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 +14 -13
  88. julee/domain/use_cases/initialize_system_data.py +88 -34
  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 +14 -14
  93. julee/fixtures/documents.yaml +4 -43
  94. julee/fixtures/knowledge_service_queries.yaml +9 -0
  95. julee/maintenance/__init__.py +1 -0
  96. julee/maintenance/release.py +243 -0
  97. julee/repositories/memory/assembly.py +6 -5
  98. julee/repositories/memory/assembly_specification.py +8 -9
  99. julee/repositories/memory/base.py +12 -11
  100. julee/repositories/memory/document.py +27 -20
  101. julee/repositories/memory/document_policy_validation.py +7 -6
  102. julee/repositories/memory/knowledge_service_config.py +8 -7
  103. julee/repositories/memory/knowledge_service_query.py +8 -7
  104. julee/repositories/memory/policy.py +6 -5
  105. julee/repositories/memory/tests/test_document.py +24 -22
  106. julee/repositories/memory/tests/test_document_policy_validation.py +2 -1
  107. julee/repositories/memory/tests/test_policy.py +2 -1
  108. julee/repositories/minio/assembly.py +4 -4
  109. julee/repositories/minio/assembly_specification.py +6 -8
  110. julee/repositories/minio/client.py +22 -25
  111. julee/repositories/minio/document.py +36 -33
  112. julee/repositories/minio/document_policy_validation.py +5 -5
  113. julee/repositories/minio/knowledge_service_config.py +6 -6
  114. julee/repositories/minio/knowledge_service_query.py +6 -9
  115. julee/repositories/minio/policy.py +4 -4
  116. julee/repositories/minio/tests/fake_client.py +11 -9
  117. julee/repositories/minio/tests/test_assembly.py +3 -1
  118. julee/repositories/minio/tests/test_assembly_specification.py +2 -1
  119. julee/repositories/minio/tests/test_client_protocol.py +5 -5
  120. julee/repositories/minio/tests/test_document.py +23 -22
  121. julee/repositories/minio/tests/test_document_policy_validation.py +3 -1
  122. julee/repositories/minio/tests/test_knowledge_service_config.py +4 -2
  123. julee/repositories/minio/tests/test_knowledge_service_query.py +3 -2
  124. julee/repositories/minio/tests/test_policy.py +3 -1
  125. julee/repositories/temporal/activities.py +5 -5
  126. julee/repositories/temporal/proxies.py +5 -5
  127. julee/services/knowledge_service/__init__.py +1 -2
  128. julee/services/knowledge_service/anthropic/knowledge_service.py +8 -7
  129. julee/services/knowledge_service/anthropic/tests/test_knowledge_service.py +11 -10
  130. julee/services/knowledge_service/factory.py +8 -8
  131. julee/services/knowledge_service/knowledge_service.py +12 -14
  132. julee/services/knowledge_service/memory/knowledge_service.py +13 -12
  133. julee/services/knowledge_service/memory/test_knowledge_service.py +10 -7
  134. julee/services/knowledge_service/test_factory.py +11 -10
  135. julee/services/temporal/activities.py +10 -10
  136. julee/services/temporal/proxies.py +2 -2
  137. julee/util/domain.py +6 -6
  138. julee/util/repos/minio/file_storage.py +8 -9
  139. julee/util/repos/temporal/client_proxies/file_storage.py +3 -4
  140. julee/util/repos/temporal/data_converter.py +6 -6
  141. julee/util/repos/temporal/minio_file_storage.py +1 -1
  142. julee/util/repos/temporal/proxies/file_storage.py +2 -3
  143. julee/util/repositories.py +4 -3
  144. julee/util/temporal/decorators.py +20 -18
  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.2.dist-info → julee-0.1.4.dist-info}/METADATA +3 -3
  153. julee-0.1.4.dist-info/RECORD +196 -0
  154. julee/fixtures/assembly_specifications.yaml +0 -70
  155. julee-0.1.2.dist-info/RECORD +0 -161
  156. {julee-0.1.2.dist-info → julee-0.1.4.dist-info}/WHEEL +0 -0
  157. {julee-0.1.2.dist-info → julee-0.1.4.dist-info}/licenses/LICENSE +0 -0
  158. {julee-0.1.2.dist-info → julee-0.1.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,185 @@
1
+ """Shared utilities for sphinx_hcd extension.
2
+
3
+ Common functions used across multiple extension modules.
4
+ """
5
+
6
+ import re
7
+
8
+ from docutils import nodes
9
+
10
+
11
+ def normalize_name(name: str) -> str:
12
+ """Normalize a name for comparison (lowercase, hyphens to spaces).
13
+
14
+ Args:
15
+ name: Name to normalize
16
+
17
+ Returns:
18
+ Normalized lowercase name with consistent spacing
19
+ """
20
+ return name.lower().replace("-", " ").replace("_", " ").strip()
21
+
22
+
23
+ def slugify(text: str) -> str:
24
+ """Create a URL-safe slug from text.
25
+
26
+ Args:
27
+ text: Text to slugify
28
+
29
+ Returns:
30
+ URL-safe slug string
31
+ """
32
+ slug = text.lower()
33
+ slug = re.sub(r"[^a-z0-9\s-]", "", slug)
34
+ slug = re.sub(r"[\s_]+", "-", slug)
35
+ slug = re.sub(r"-+", "-", slug)
36
+ return slug.strip("-")
37
+
38
+
39
+ def kebab_to_snake(name: str) -> str:
40
+ """Convert kebab-case to snake_case for Python module names.
41
+
42
+ Args:
43
+ name: Kebab-case name (e.g., 'audit-analysis')
44
+
45
+ Returns:
46
+ Snake_case name (e.g., 'audit_analysis')
47
+ """
48
+ return name.replace("-", "_")
49
+
50
+
51
+ def parse_list_option(value: str) -> list[str]:
52
+ """Parse a newline-separated list option with optional bullet prefixes.
53
+
54
+ Handles RST-style lists like:
55
+ - First item
56
+ - Second item with (commas, inside)
57
+
58
+ Does NOT split on commas to preserve items containing parenthetical lists.
59
+
60
+ Args:
61
+ value: Raw option string
62
+
63
+ Returns:
64
+ List of stripped item strings
65
+ """
66
+ if not value:
67
+ return []
68
+ items = []
69
+ for line in value.strip().split("\n"):
70
+ item = line.strip().lstrip("- ")
71
+ if item:
72
+ items.append(item)
73
+ return items
74
+
75
+
76
+ def parse_csv_option(value: str) -> list[str]:
77
+ """Parse a comma-separated list option.
78
+
79
+ Args:
80
+ value: Raw option string
81
+
82
+ Returns:
83
+ List of stripped item strings
84
+ """
85
+ if not value:
86
+ return []
87
+ return [item.strip() for item in value.split(",") if item.strip()]
88
+
89
+
90
+ def parse_integration_options(value: str) -> list[dict]:
91
+ """Parse integration options with optional descriptions.
92
+
93
+ Supports format: integration-slug (description of data)
94
+ Example: pilot-data-collection (CMA documents, audit reports)
95
+
96
+ Args:
97
+ value: Raw option string
98
+
99
+ Returns:
100
+ List of dicts with 'slug' and 'description' keys
101
+ """
102
+ if not value:
103
+ return []
104
+
105
+ items = []
106
+ for line in value.strip().split("\n"):
107
+ line = line.strip().lstrip("- ")
108
+ if not line:
109
+ continue
110
+
111
+ # Parse: slug (description) or just slug
112
+ match = re.match(r"^([a-z0-9-]+)\s*(?:\(([^)]+)\))?$", line.strip())
113
+ if match:
114
+ items.append(
115
+ {
116
+ "slug": match.group(1),
117
+ "description": match.group(2).strip() if match.group(2) else None,
118
+ }
119
+ )
120
+ else:
121
+ # Fallback: treat whole line as slug
122
+ items.append(
123
+ {
124
+ "slug": line.strip(),
125
+ "description": None,
126
+ }
127
+ )
128
+
129
+ return items
130
+
131
+
132
+ def path_to_root(docname: str) -> str:
133
+ """Calculate relative path from a document to the docs root.
134
+
135
+ Args:
136
+ docname: Document name (e.g., 'users/journeys/build-vocabulary')
137
+
138
+ Returns:
139
+ Relative path prefix (e.g., '../../')
140
+ """
141
+ depth = docname.count("/")
142
+ return "../" * depth
143
+
144
+
145
+ def make_reference(uri: str, text: str, is_code: bool = False) -> nodes.reference:
146
+ """Create a reference node.
147
+
148
+ Args:
149
+ uri: Target URI
150
+ text: Link text
151
+ is_code: If True, render text as code/literal
152
+
153
+ Returns:
154
+ docutils reference node
155
+ """
156
+ ref = nodes.reference("", "", refuri=uri)
157
+ if is_code:
158
+ ref += nodes.literal(text=text)
159
+ else:
160
+ ref += nodes.Text(text)
161
+ return ref
162
+
163
+
164
+ def make_internal_link(
165
+ docname: str,
166
+ target_doc: str,
167
+ text: str,
168
+ anchor: str | None = None,
169
+ ) -> nodes.reference:
170
+ """Create an internal document link with proper relative path.
171
+
172
+ Args:
173
+ docname: Current document name
174
+ target_doc: Target document path (e.g., 'applications/staff-portal')
175
+ text: Link text
176
+ anchor: Optional anchor within target page
177
+
178
+ Returns:
179
+ docutils reference node
180
+ """
181
+ prefix = path_to_root(docname)
182
+ uri = f"{prefix}{target_doc}.html"
183
+ if anchor:
184
+ uri = f"{uri}#{anchor}"
185
+ return make_reference(uri, text)
@@ -10,11 +10,6 @@ Re-exports commonly used models for convenient importing:
10
10
  """
11
11
 
12
12
  # Document models
13
- from .document import Document, DocumentStatus
14
-
15
- # Custom field types
16
- from .custom_fields.content_stream import ContentStream
17
-
18
13
  # Assembly models
19
14
  from .assembly import Assembly, AssemblyStatus
20
15
  from .assembly_specification import (
@@ -23,11 +18,15 @@ from .assembly_specification import (
23
18
  KnowledgeServiceQuery,
24
19
  )
25
20
 
21
+ # Custom field types
22
+ from .custom_fields.content_stream import ContentStream
23
+ from .document import Document, DocumentStatus
24
+
26
25
  # Configuration models
27
26
  from .knowledge_service_config import KnowledgeServiceConfig
28
27
 
29
28
  # Policy models
30
- from .policy import Policy, PolicyStatus, DocumentPolicyValidation
29
+ from .policy import DocumentPolicyValidation, Policy, PolicyStatus
31
30
 
32
31
  __all__ = [
33
32
  # Document models
@@ -12,11 +12,11 @@ All domain models use Pydantic BaseModel for validation, serialization,
12
12
  and type safety, following the patterns established in the sample project.
13
13
  """
14
14
 
15
- from pydantic import BaseModel, Field, field_validator
16
- from typing import Optional
17
15
  from datetime import datetime, timezone
18
16
  from enum import Enum
19
17
 
18
+ from pydantic import BaseModel, Field, field_validator
19
+
20
20
 
21
21
  class AssemblyStatus(str, Enum):
22
22
  """Status of an assembly process."""
@@ -52,16 +52,16 @@ class Assembly(BaseModel):
52
52
 
53
53
  # Assembly process tracking
54
54
  status: AssemblyStatus = AssemblyStatus.PENDING
55
- assembled_document_id: Optional[str] = Field(
55
+ assembled_document_id: str | None = Field(
56
56
  default=None,
57
57
  description="ID of the assembled document produced by this assembly",
58
58
  )
59
59
 
60
60
  # Assembly metadata
61
- created_at: Optional[datetime] = Field(
61
+ created_at: datetime | None = Field(
62
62
  default_factory=lambda: datetime.now(timezone.utc)
63
63
  )
64
- updated_at: Optional[datetime] = Field(
64
+ updated_at: datetime | None = Field(
65
65
  default_factory=lambda: datetime.now(timezone.utc)
66
66
  )
67
67
 
@@ -89,8 +89,8 @@ class Assembly(BaseModel):
89
89
  @field_validator("assembled_document_id")
90
90
  @classmethod
91
91
  def assembled_document_id_must_not_be_empty_if_provided(
92
- cls, v: Optional[str]
93
- ) -> Optional[str]:
92
+ cls, v: str | None
93
+ ) -> str | None:
94
94
  if v is not None and (not v or not v.strip()):
95
95
  raise ValueError("Assembled document ID cannot be empty string")
96
96
  return v.strip() if v else None
@@ -6,9 +6,10 @@ Assembly domain objects with sensible defaults.
6
6
  """
7
7
 
8
8
  from datetime import datetime, timezone
9
+
9
10
  from factory.base import Factory
10
- from factory.faker import Faker
11
11
  from factory.declarations import LazyFunction
12
+ from factory.faker import Faker
12
13
 
13
14
  from julee.domain.models.assembly import (
14
15
  Assembly,
@@ -19,11 +19,14 @@ Design decisions documented:
19
19
  - Timestamps are automatically set with timezone-aware defaults
20
20
  """
21
21
 
22
- import pytest
23
22
  import json
24
23
  from datetime import datetime, timezone
25
24
 
25
+ import pytest
26
+ from pydantic import ValidationError
27
+
26
28
  from julee.domain.models.assembly import Assembly, AssemblyStatus
29
+
27
30
  from .factories import AssemblyFactory
28
31
 
29
32
 
@@ -79,7 +82,7 @@ class TestAssemblyInstantiation:
79
82
  assert assembly.updated_at is not None
80
83
  else:
81
84
  # Should raise validation error
82
- with pytest.raises(Exception): # Could be ValueError or ValidationError
85
+ with pytest.raises((ValueError, ValidationError)):
83
86
  Assembly(
84
87
  assembly_id=assembly_id,
85
88
  assembly_specification_id=assembly_specification_id,
@@ -222,7 +225,7 @@ class TestAssemblyFieldValidation:
222
225
  assert valid_assembly.assembly_id == "valid-id"
223
226
 
224
227
  # Invalid cases
225
- with pytest.raises(Exception):
228
+ with pytest.raises((ValueError, ValidationError)):
226
229
  Assembly(
227
230
  assembly_id="",
228
231
  assembly_specification_id="spec-id",
@@ -230,7 +233,7 @@ class TestAssemblyFieldValidation:
230
233
  workflow_id="test-workflow-123",
231
234
  )
232
235
 
233
- with pytest.raises(Exception):
236
+ with pytest.raises((ValueError, ValidationError)):
234
237
  Assembly(
235
238
  assembly_id=" ",
236
239
  assembly_specification_id="spec-id",
@@ -250,7 +253,7 @@ class TestAssemblyFieldValidation:
250
253
  assert valid_assembly.assembly_specification_id == "valid-spec-id"
251
254
 
252
255
  # Invalid cases
253
- with pytest.raises(Exception):
256
+ with pytest.raises((ValueError, ValidationError)):
254
257
  Assembly(
255
258
  assembly_id="asm-id",
256
259
  assembly_specification_id="",
@@ -258,7 +261,7 @@ class TestAssemblyFieldValidation:
258
261
  workflow_id="test-workflow-123",
259
262
  )
260
263
 
261
- with pytest.raises(Exception):
264
+ with pytest.raises((ValueError, ValidationError)):
262
265
  Assembly(
263
266
  assembly_id="asm-id",
264
267
  assembly_specification_id=" ",
@@ -278,7 +281,7 @@ class TestAssemblyFieldValidation:
278
281
  assert valid_assembly.input_document_id == "valid-doc-id"
279
282
 
280
283
  # Invalid cases
281
- with pytest.raises(Exception):
284
+ with pytest.raises((ValueError, ValidationError)):
282
285
  Assembly(
283
286
  assembly_id="asm-id",
284
287
  assembly_specification_id="spec-id",
@@ -286,7 +289,7 @@ class TestAssemblyFieldValidation:
286
289
  workflow_id="test-workflow-123",
287
290
  )
288
291
 
289
- with pytest.raises(Exception):
292
+ with pytest.raises((ValueError, ValidationError)):
290
293
  Assembly(
291
294
  assembly_id="asm-id",
292
295
  assembly_specification_id="spec-id",
@@ -345,7 +348,7 @@ class TestAssemblyDocumentManagement:
345
348
  assert none_assembly.assembled_document_id is None
346
349
 
347
350
  # Invalid cases - empty string
348
- with pytest.raises(Exception):
351
+ with pytest.raises((ValueError, ValidationError)):
349
352
  Assembly(
350
353
  assembly_id="asm-id",
351
354
  assembly_specification_id="spec-id",
@@ -355,7 +358,7 @@ class TestAssemblyDocumentManagement:
355
358
  )
356
359
 
357
360
  # Invalid cases - whitespace only
358
- with pytest.raises(Exception):
361
+ with pytest.raises((ValueError, ValidationError)):
359
362
  Assembly(
360
363
  assembly_id="asm-id",
361
364
  assembly_specification_id="spec-id",
@@ -391,7 +394,7 @@ class TestAssemblyWorkflowIdValidation:
391
394
  assert valid_assembly.workflow_id == "valid-workflow-id"
392
395
 
393
396
  # Invalid cases - empty string
394
- with pytest.raises(Exception):
397
+ with pytest.raises((ValueError, ValidationError)):
395
398
  Assembly(
396
399
  assembly_id="asm-id",
397
400
  assembly_specification_id="spec-id",
@@ -400,7 +403,7 @@ class TestAssemblyWorkflowIdValidation:
400
403
  )
401
404
 
402
405
  # Invalid cases - whitespace only
403
- with pytest.raises(Exception):
406
+ with pytest.raises((ValueError, ValidationError)):
404
407
  Assembly(
405
408
  assembly_id="asm-id",
406
409
  assembly_specification_id="spec-id",
@@ -421,7 +424,7 @@ class TestAssemblyWorkflowIdValidation:
421
424
  def test_workflow_id_required(self) -> None:
422
425
  """Test that workflow_id is required."""
423
426
  # workflow_id is required and cannot be omitted
424
- with pytest.raises(Exception):
427
+ with pytest.raises((ValueError, ValidationError)):
425
428
  Assembly( # type: ignore[call-arg]
426
429
  assembly_id="asm-id",
427
430
  assembly_specification_id="spec-id",
@@ -13,12 +13,13 @@ All domain models use Pydantic BaseModel for validation, serialization,
13
13
  and type safety, following the patterns established in the sample project.
14
14
  """
15
15
 
16
- from pydantic import BaseModel, Field, field_validator
17
- from typing import Optional, Dict, Any
18
16
  from datetime import datetime, timezone
19
17
  from enum import Enum
20
- import jsonschema
18
+ from typing import Any
19
+
21
20
  import jsonpointer # type: ignore
21
+ import jsonschema
22
+ from pydantic import BaseModel, Field, field_validator
22
23
 
23
24
 
24
25
  class AssemblySpecificationStatus(str, Enum):
@@ -56,14 +57,14 @@ class AssemblySpecification(BaseModel):
56
57
  "service for document-assembly matching"
57
58
  )
58
59
 
59
- jsonschema: Dict[str, Any] = Field(
60
+ jsonschema: dict[str, Any] = Field(
60
61
  description="JSON Schema defining the structure of data to be "
61
62
  "extracted for this assembly"
62
63
  )
63
64
 
64
65
  # AssemblySpecification configuration
65
66
  status: AssemblySpecificationStatus = AssemblySpecificationStatus.ACTIVE
66
- knowledge_service_queries: Dict[str, str] = Field(
67
+ knowledge_service_queries: dict[str, str] = Field(
67
68
  default_factory=dict,
68
69
  description="Mapping from JSON Pointer paths to "
69
70
  "KnowledgeServiceQuery IDs. Keys are JSON Pointer strings "
@@ -73,10 +74,10 @@ class AssemblySpecification(BaseModel):
73
74
 
74
75
  # AssemblySpecification metadata
75
76
  version: str = Field(default="0.1.0", description="Assembly definition version")
76
- created_at: Optional[datetime] = Field(
77
+ created_at: datetime | None = Field(
77
78
  default_factory=lambda: datetime.now(timezone.utc)
78
79
  )
79
- updated_at: Optional[datetime] = Field(
80
+ updated_at: datetime | None = Field(
80
81
  default_factory=lambda: datetime.now(timezone.utc)
81
82
  )
82
83
  # May later add a detailed description, change log, additional metadata
@@ -105,7 +106,7 @@ class AssemblySpecification(BaseModel):
105
106
 
106
107
  @field_validator("jsonschema")
107
108
  @classmethod
108
- def jsonschema_must_be_valid(cls, v: Dict[str, Any]) -> Dict[str, Any]:
109
+ def jsonschema_must_be_valid(cls, v: dict[str, Any]) -> dict[str, Any]:
109
110
  if not isinstance(v, dict):
110
111
  raise ValueError("JSON Schema must be a dictionary")
111
112
 
@@ -124,8 +125,8 @@ class AssemblySpecification(BaseModel):
124
125
  @field_validator("knowledge_service_queries")
125
126
  @classmethod
126
127
  def knowledge_service_queries_must_be_valid(
127
- cls, v: Dict[str, str], info: Any
128
- ) -> Dict[str, str]:
128
+ cls, v: dict[str, str], info: Any
129
+ ) -> dict[str, str]:
129
130
  if not isinstance(v, dict):
130
131
  raise ValueError("Knowledge service queries must be a dictionary")
131
132
 
@@ -15,9 +15,10 @@ All domain models use Pydantic BaseModel for validation, serialization,
15
15
  and type safety, following the patterns established in the sample project.
16
16
  """
17
17
 
18
- from pydantic import BaseModel, Field, field_validator
19
- from typing import Optional, Dict, Any
20
18
  from datetime import datetime, timezone
19
+ from typing import Any
20
+
21
+ from pydantic import BaseModel, Field, field_validator
21
22
 
22
23
 
23
24
  class KnowledgeServiceQuery(BaseModel):
@@ -77,13 +78,13 @@ class KnowledgeServiceQuery(BaseModel):
77
78
  )
78
79
 
79
80
  # Service-specific configuration
80
- query_metadata: Optional[Dict[str, Any]] = Field(
81
+ query_metadata: dict[str, Any] | None = Field(
81
82
  default_factory=dict,
82
83
  description="Service-specific metadata and configuration options "
83
84
  "such as model selection, temperature, max_tokens, etc. "
84
85
  "The structure depends on the specific knowledge service being used.",
85
86
  )
86
- assistant_prompt: Optional[str] = Field(
87
+ assistant_prompt: str | None = Field(
87
88
  default=None,
88
89
  description="Optional assistant message content to constrain "
89
90
  "or prime the model's response. This is added as the final "
@@ -91,10 +92,10 @@ class KnowledgeServiceQuery(BaseModel):
91
92
  "allowing control over response format and structure.",
92
93
  )
93
94
 
94
- created_at: Optional[datetime] = Field(
95
+ created_at: datetime | None = Field(
95
96
  default_factory=lambda: datetime.now(timezone.utc)
96
97
  )
97
- updated_at: Optional[datetime] = Field(
98
+ updated_at: datetime | None = Field(
98
99
  default_factory=lambda: datetime.now(timezone.utc)
99
100
  )
100
101
 
@@ -7,9 +7,10 @@ AssemblySpecification domain objects with sensible defaults.
7
7
 
8
8
  from datetime import datetime, timezone
9
9
  from typing import Any
10
+
10
11
  from factory.base import Factory
11
- from factory.faker import Faker
12
12
  from factory.declarations import LazyAttribute, LazyFunction
13
+ from factory.faker import Faker
13
14
 
14
15
  from julee.domain.models.assembly_specification import (
15
16
  AssemblySpecification,
@@ -18,14 +18,17 @@ Design decisions documented:
18
18
  - Status defaults to ACTIVE
19
19
  """
20
20
 
21
- import pytest
22
21
  import json
23
- from typing import Dict, Any
22
+ from typing import Any
23
+
24
+ import pytest
25
+ from pydantic import ValidationError
24
26
 
25
27
  from julee.domain.models.assembly_specification import (
26
28
  AssemblySpecification,
27
29
  AssemblySpecificationStatus,
28
30
  )
31
+
29
32
  from .factories import AssemblyFactory
30
33
 
31
34
 
@@ -136,7 +139,7 @@ class TestAssemblyInstantiation:
136
139
  assembly_specification_id: str,
137
140
  name: str,
138
141
  applicability: str,
139
- jsonschema: Dict[str, Any],
142
+ jsonschema: dict[str, Any],
140
143
  expected_success: bool,
141
144
  ) -> None:
142
145
  """Test assembly creation with various field validation scenarios."""
@@ -158,7 +161,7 @@ class TestAssemblyInstantiation:
158
161
  assert assembly.version == "0.1.0" # Default
159
162
  else:
160
163
  # Should raise validation error
161
- with pytest.raises(Exception): # Could be ValueError or ValidationError
164
+ with pytest.raises((ValueError, ValidationError)):
162
165
  AssemblySpecification(
163
166
  assembly_specification_id=assembly_specification_id,
164
167
  name=name,
@@ -213,7 +216,7 @@ class TestAssemblyKnowledgeServiceQueriesValidation:
213
216
  assert assembly.knowledge_service_queries == knowledge_service_queries
214
217
  else:
215
218
  # Should raise validation error
216
- with pytest.raises(Exception):
219
+ with pytest.raises((ValueError, ValidationError)):
217
220
  AssemblySpecification(
218
221
  assembly_specification_id="test-id",
219
222
  name="Test Assembly",
@@ -486,5 +489,5 @@ class TestAssemblyVersionValidation:
486
489
  assembly = AssemblyFactory.build(version=version)
487
490
  assert assembly.version == version.strip()
488
491
  else:
489
- with pytest.raises(Exception):
492
+ with pytest.raises((ValueError, ValidationError)):
490
493
  AssemblyFactory.build(version=version)
@@ -19,10 +19,12 @@ Design decisions documented:
19
19
  """
20
20
 
21
21
  import pytest
22
+ from pydantic import ValidationError
22
23
 
23
24
  from julee.domain.models.assembly_specification import (
24
25
  KnowledgeServiceQuery,
25
26
  )
27
+
26
28
  from .factories import KnowledgeServiceQueryFactory
27
29
 
28
30
 
@@ -137,7 +139,7 @@ class TestKnowledgeServiceQueryInstantiation:
137
139
  assert query.prompt == prompt.strip()
138
140
  else:
139
141
  # Should raise validation error
140
- with pytest.raises(Exception): # Could be ValueError or ValidationError
142
+ with pytest.raises((ValueError, ValidationError)):
141
143
  KnowledgeServiceQuery(
142
144
  query_id=query_id,
143
145
  name=name,
@@ -6,10 +6,11 @@ validation
6
6
  for specialized data types used in the document processing workflow.
7
7
  """
8
8
 
9
+ import io
10
+ from typing import Any
11
+
9
12
  from pydantic import GetCoreSchemaHandler
10
13
  from pydantic_core import core_schema
11
- from typing import Any
12
- import io
13
14
 
14
15
 
15
16
  class ContentStream:
@@ -12,10 +12,11 @@ Design decisions documented:
12
12
  - ContentStream works with Pydantic validation without arbitrary_types_allowed
13
13
  """
14
14
 
15
- import pytest
16
15
  import io
17
16
  from typing import Any
18
17
 
18
+ import pytest
19
+
19
20
  from julee.domain.models.custom_fields.content_stream import (
20
21
  ContentStream,
21
22
  )