julee 0.1.4__py3-none-any.whl → 0.1.5__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/__init__.py +1 -1
- julee/api/tests/routers/test_assembly_specifications.py +2 -0
- julee/api/tests/routers/test_documents.py +2 -0
- julee/api/tests/routers/test_knowledge_service_configs.py +2 -0
- julee/api/tests/routers/test_knowledge_service_queries.py +2 -0
- julee/api/tests/routers/test_system.py +2 -0
- julee/api/tests/routers/test_workflows.py +2 -0
- julee/api/tests/test_app.py +2 -0
- julee/api/tests/test_dependencies.py +2 -0
- julee/api/tests/test_requests.py +2 -0
- julee/contrib/polling/__init__.py +22 -19
- julee/contrib/polling/apps/__init__.py +17 -0
- julee/contrib/polling/apps/worker/__init__.py +17 -0
- julee/contrib/polling/apps/worker/pipelines.py +288 -0
- julee/contrib/polling/domain/__init__.py +7 -9
- julee/contrib/polling/domain/models/__init__.py +6 -7
- julee/contrib/polling/domain/models/polling_config.py +18 -1
- julee/contrib/polling/domain/services/__init__.py +6 -5
- julee/contrib/polling/domain/services/poller.py +1 -1
- julee/contrib/polling/infrastructure/__init__.py +9 -8
- julee/contrib/polling/infrastructure/services/__init__.py +6 -5
- julee/contrib/polling/infrastructure/services/polling/__init__.py +6 -5
- julee/contrib/polling/infrastructure/services/polling/http/__init__.py +6 -5
- julee/contrib/polling/infrastructure/services/polling/http/http_poller_service.py +5 -2
- julee/contrib/polling/infrastructure/temporal/__init__.py +12 -12
- julee/contrib/polling/infrastructure/temporal/activities.py +1 -1
- julee/contrib/polling/infrastructure/temporal/manager.py +291 -0
- julee/contrib/polling/infrastructure/temporal/proxies.py +1 -1
- julee/contrib/polling/tests/unit/apps/worker/test_pipelines.py +580 -0
- julee/contrib/polling/tests/unit/infrastructure/services/polling/http/test_http_poller_service.py +40 -2
- julee/contrib/polling/tests/unit/infrastructure/temporal/__init__.py +7 -0
- julee/contrib/polling/tests/unit/infrastructure/temporal/test_manager.py +475 -0
- julee/domain/models/assembly/tests/test_assembly.py +2 -0
- julee/domain/models/assembly_specification/tests/test_assembly_specification.py +2 -0
- julee/domain/models/assembly_specification/tests/test_knowledge_service_query.py +2 -0
- julee/domain/models/custom_fields/tests/test_custom_fields.py +2 -0
- julee/domain/models/document/tests/test_document.py +2 -0
- julee/domain/models/policy/tests/test_document_policy_validation.py +2 -0
- julee/domain/models/policy/tests/test_policy.py +2 -0
- julee/domain/use_cases/tests/test_extract_assemble_data.py +2 -0
- julee/domain/use_cases/tests/test_initialize_system_data.py +2 -0
- julee/domain/use_cases/tests/test_validate_document.py +2 -0
- julee/maintenance/release.py +10 -5
- julee/repositories/memory/tests/test_document.py +2 -0
- julee/repositories/memory/tests/test_document_policy_validation.py +2 -0
- julee/repositories/memory/tests/test_policy.py +2 -0
- julee/repositories/minio/tests/test_assembly.py +2 -0
- julee/repositories/minio/tests/test_assembly_specification.py +2 -0
- julee/repositories/minio/tests/test_client_protocol.py +3 -0
- julee/repositories/minio/tests/test_document.py +2 -0
- julee/repositories/minio/tests/test_document_policy_validation.py +2 -0
- julee/repositories/minio/tests/test_knowledge_service_config.py +2 -0
- julee/repositories/minio/tests/test_knowledge_service_query.py +2 -0
- julee/repositories/minio/tests/test_policy.py +2 -0
- julee/services/knowledge_service/anthropic/tests/test_knowledge_service.py +2 -0
- julee/services/knowledge_service/memory/test_knowledge_service.py +2 -0
- julee/services/knowledge_service/test_factory.py +2 -0
- julee/util/tests/test_decorators.py +2 -0
- julee-0.1.5.dist-info/METADATA +103 -0
- {julee-0.1.4.dist-info → julee-0.1.5.dist-info}/RECORD +63 -56
- julee-0.1.4.dist-info/METADATA +0 -197
- {julee-0.1.4.dist-info → julee-0.1.5.dist-info}/WHEEL +0 -0
- {julee-0.1.4.dist-info → julee-0.1.5.dist-info}/licenses/LICENSE +0 -0
- {julee-0.1.4.dist-info → julee-0.1.5.dist-info}/top_level.txt +0 -0
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Temporal
|
|
2
|
+
Temporal integration for the polling contrib module.
|
|
3
3
|
|
|
4
|
-
This module
|
|
5
|
-
|
|
4
|
+
This module provides the Temporal integration layer for polling operations,
|
|
5
|
+
including workflow proxies for calling polling activities from workflows and
|
|
6
6
|
activity name constants.
|
|
7
7
|
|
|
8
8
|
This keeps all polling-temporal integration within the contrib module,
|
|
9
9
|
maintaining proper dependency direction (contrib imports from core, not vice versa).
|
|
10
|
-
"""
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
No re-exports to avoid import chains that pull non-deterministic code
|
|
12
|
+
into Temporal workflows. Import directly from specific modules:
|
|
13
|
+
|
|
14
|
+
- from julee.contrib.polling.infrastructure.temporal.activity_names import POLLING_SERVICE_ACTIVITY_BASE
|
|
15
|
+
- from julee.contrib.polling.infrastructure.temporal.manager import PollingManager
|
|
16
|
+
- from julee.contrib.polling.infrastructure.temporal.proxies import WorkflowPollerServiceProxy
|
|
17
|
+
- from julee.contrib.polling.infrastructure.temporal.activities import TemporalPollerService
|
|
18
|
+
"""
|
|
15
19
|
|
|
16
|
-
__all__ = [
|
|
17
|
-
"TemporalPollerService",
|
|
18
|
-
"POLLING_SERVICE_ACTIVITY_BASE",
|
|
19
|
-
"WorkflowPollerServiceProxy",
|
|
20
|
-
]
|
|
20
|
+
__all__ = []
|
|
@@ -14,7 +14,7 @@ import logging
|
|
|
14
14
|
|
|
15
15
|
from julee.util.temporal.decorators import temporal_activity_registration
|
|
16
16
|
|
|
17
|
-
from ..services.polling.http import HttpPollerService
|
|
17
|
+
from ..services.polling.http.http_poller_service import HttpPollerService
|
|
18
18
|
from .activity_names import POLLING_SERVICE_ACTIVITY_BASE
|
|
19
19
|
|
|
20
20
|
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PollingManager for high-level HTTP endpoint polling operations.
|
|
3
|
+
|
|
4
|
+
This module provides a simple, framework-agnostic API for managing
|
|
5
|
+
HTTP endpoint polling with automatic change detection and pipeline
|
|
6
|
+
triggering. It abstracts away the underlying Temporal scheduling
|
|
7
|
+
implementation.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from datetime import timedelta
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from temporalio.client import (
|
|
15
|
+
Client,
|
|
16
|
+
Schedule,
|
|
17
|
+
ScheduleActionStartWorkflow,
|
|
18
|
+
ScheduleAlreadyRunningError,
|
|
19
|
+
ScheduleIntervalSpec,
|
|
20
|
+
ScheduleSpec,
|
|
21
|
+
ScheduleUpdate,
|
|
22
|
+
ScheduleUpdateInput,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
from julee.contrib.polling.domain.models.polling_config import PollingConfig
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class PollingManager:
|
|
31
|
+
"""
|
|
32
|
+
High-level manager for HTTP endpoint polling operations.
|
|
33
|
+
|
|
34
|
+
This class provides a simple API for starting and stopping polling
|
|
35
|
+
operations using Temporal schedules for reliable execution. Users
|
|
36
|
+
must provide a Temporal client during initialization, following the
|
|
37
|
+
same pattern as other Julee infrastructure components.
|
|
38
|
+
|
|
39
|
+
The manager handles:
|
|
40
|
+
- Creating and managing Temporal schedules for polling
|
|
41
|
+
- Tracking active polling operations
|
|
42
|
+
- Providing status and control operations (pause/resume/stop)
|
|
43
|
+
|
|
44
|
+
Example:
|
|
45
|
+
# Using default task queue
|
|
46
|
+
manager = PollingManager(temporal_client)
|
|
47
|
+
|
|
48
|
+
# Using custom task queue
|
|
49
|
+
manager = PollingManager(temporal_client, task_queue="my-polling-queue")
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
temporal_client: Client | None = None,
|
|
55
|
+
task_queue: str = "julee-polling-queue",
|
|
56
|
+
) -> None:
|
|
57
|
+
"""
|
|
58
|
+
Initialize the polling manager.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
temporal_client: Temporal client for schedule management.
|
|
62
|
+
Typically created at application startup level
|
|
63
|
+
(worker, API) and passed to the manager.
|
|
64
|
+
task_queue: Task queue name for workflow execution.
|
|
65
|
+
Defaults to "julee-polling-queue".
|
|
66
|
+
"""
|
|
67
|
+
self._temporal_client = temporal_client
|
|
68
|
+
self._task_queue = task_queue
|
|
69
|
+
self._active_polls: dict[str, dict[str, Any]] = {}
|
|
70
|
+
|
|
71
|
+
async def start_polling(
|
|
72
|
+
self,
|
|
73
|
+
endpoint_id: str,
|
|
74
|
+
config: PollingConfig,
|
|
75
|
+
interval_seconds: int,
|
|
76
|
+
downstream_pipeline: str | None = None,
|
|
77
|
+
) -> str:
|
|
78
|
+
"""
|
|
79
|
+
Start polling an HTTP endpoint at regular intervals.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
endpoint_id: Unique identifier for this polling operation
|
|
83
|
+
config: Configuration for the polling operation
|
|
84
|
+
interval_seconds: How often to poll (in seconds)
|
|
85
|
+
downstream_pipeline: Optional pipeline to trigger when new data detected
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Schedule ID that was created
|
|
89
|
+
|
|
90
|
+
Raises:
|
|
91
|
+
ValueError: If endpoint_id is already being polled
|
|
92
|
+
RuntimeError: If Temporal client is not available
|
|
93
|
+
"""
|
|
94
|
+
if endpoint_id in self._active_polls:
|
|
95
|
+
raise ValueError(f"Endpoint {endpoint_id} is already being polled")
|
|
96
|
+
|
|
97
|
+
if self._temporal_client is None:
|
|
98
|
+
raise RuntimeError("Temporal client not available")
|
|
99
|
+
|
|
100
|
+
schedule_id = f"poll-{endpoint_id}"
|
|
101
|
+
|
|
102
|
+
schedule = Schedule(
|
|
103
|
+
action=ScheduleActionStartWorkflow(
|
|
104
|
+
"NewDataDetectionPipeline",
|
|
105
|
+
args=[config, downstream_pipeline],
|
|
106
|
+
id=f"{schedule_id}-{{.timestamp}}",
|
|
107
|
+
task_queue=self._task_queue,
|
|
108
|
+
),
|
|
109
|
+
spec=ScheduleSpec(
|
|
110
|
+
intervals=[
|
|
111
|
+
ScheduleIntervalSpec(every=timedelta(seconds=interval_seconds))
|
|
112
|
+
]
|
|
113
|
+
),
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
await self._temporal_client.create_schedule(
|
|
118
|
+
id=schedule_id, schedule=schedule
|
|
119
|
+
)
|
|
120
|
+
logger.info(
|
|
121
|
+
f"Created new schedule {schedule_id} for endpoint {endpoint_id}"
|
|
122
|
+
)
|
|
123
|
+
except ScheduleAlreadyRunningError:
|
|
124
|
+
# Update existing schedule preserving history
|
|
125
|
+
logger.info(f"Updating existing schedule {schedule_id}")
|
|
126
|
+
schedule_handle = self._temporal_client.get_schedule_handle(schedule_id)
|
|
127
|
+
|
|
128
|
+
# Create update function that modifies the schedule
|
|
129
|
+
async def update_schedule_callback(
|
|
130
|
+
input: ScheduleUpdateInput,
|
|
131
|
+
) -> ScheduleUpdate:
|
|
132
|
+
# Update the schedule with new configuration
|
|
133
|
+
updated_schedule = input.description.schedule
|
|
134
|
+
updated_schedule.action = schedule.action
|
|
135
|
+
updated_schedule.spec = schedule.spec
|
|
136
|
+
return ScheduleUpdate(schedule=updated_schedule)
|
|
137
|
+
|
|
138
|
+
await schedule_handle.update(update_schedule_callback)
|
|
139
|
+
logger.info(f"Updated schedule {schedule_id} for endpoint {endpoint_id}")
|
|
140
|
+
|
|
141
|
+
# Track the active polling operation
|
|
142
|
+
self._active_polls[endpoint_id] = {
|
|
143
|
+
"schedule_id": schedule_id,
|
|
144
|
+
"config": config,
|
|
145
|
+
"interval_seconds": interval_seconds,
|
|
146
|
+
"downstream_pipeline": downstream_pipeline,
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return schedule_id
|
|
150
|
+
|
|
151
|
+
async def stop_polling(self, endpoint_id: str) -> bool:
|
|
152
|
+
"""
|
|
153
|
+
Stop polling an endpoint.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
endpoint_id: The endpoint ID to stop polling
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
True if polling was stopped, False if not found
|
|
160
|
+
|
|
161
|
+
Raises:
|
|
162
|
+
RuntimeError: If Temporal client is not available
|
|
163
|
+
"""
|
|
164
|
+
if endpoint_id not in self._active_polls:
|
|
165
|
+
return False
|
|
166
|
+
|
|
167
|
+
if self._temporal_client is None:
|
|
168
|
+
raise RuntimeError("Temporal client not available")
|
|
169
|
+
|
|
170
|
+
poll_info = self._active_polls[endpoint_id]
|
|
171
|
+
schedule_id = poll_info["schedule_id"]
|
|
172
|
+
|
|
173
|
+
# Delete the Temporal schedule
|
|
174
|
+
schedule_handle = self._temporal_client.get_schedule_handle(schedule_id)
|
|
175
|
+
await schedule_handle.delete()
|
|
176
|
+
|
|
177
|
+
# Remove from tracking
|
|
178
|
+
del self._active_polls[endpoint_id]
|
|
179
|
+
|
|
180
|
+
return True
|
|
181
|
+
|
|
182
|
+
async def list_active_polling(self) -> list[dict[str, Any]]:
|
|
183
|
+
"""
|
|
184
|
+
List all active polling operations.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
List of dictionaries containing polling operation details
|
|
188
|
+
"""
|
|
189
|
+
active_polls = []
|
|
190
|
+
|
|
191
|
+
for endpoint_id, poll_info in self._active_polls.items():
|
|
192
|
+
active_polls.append(
|
|
193
|
+
{
|
|
194
|
+
"endpoint_id": endpoint_id,
|
|
195
|
+
"schedule_id": poll_info["schedule_id"],
|
|
196
|
+
"interval_seconds": poll_info["interval_seconds"],
|
|
197
|
+
"endpoint_identifier": poll_info["config"].endpoint_identifier,
|
|
198
|
+
"polling_protocol": poll_info["config"].polling_protocol.value,
|
|
199
|
+
"downstream_pipeline": poll_info.get("downstream_pipeline"),
|
|
200
|
+
}
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
return active_polls
|
|
204
|
+
|
|
205
|
+
async def get_polling_status(self, endpoint_id: str) -> dict[str, Any] | None:
|
|
206
|
+
"""
|
|
207
|
+
Get the status of a specific polling operation.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
endpoint_id: The endpoint ID to check
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
Dictionary with status information or None if not found
|
|
214
|
+
|
|
215
|
+
Raises:
|
|
216
|
+
RuntimeError: If Temporal client is not available
|
|
217
|
+
"""
|
|
218
|
+
if endpoint_id not in self._active_polls:
|
|
219
|
+
return None
|
|
220
|
+
|
|
221
|
+
if self._temporal_client is None:
|
|
222
|
+
raise RuntimeError("Temporal client not available")
|
|
223
|
+
|
|
224
|
+
poll_info = self._active_polls[endpoint_id]
|
|
225
|
+
schedule_id = poll_info["schedule_id"]
|
|
226
|
+
|
|
227
|
+
# Get schedule information from Temporal
|
|
228
|
+
schedule_handle = self._temporal_client.get_schedule_handle(schedule_id)
|
|
229
|
+
schedule_description = await schedule_handle.describe()
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
"endpoint_id": endpoint_id,
|
|
233
|
+
"schedule_id": schedule_id,
|
|
234
|
+
"interval_seconds": poll_info["interval_seconds"],
|
|
235
|
+
"is_paused": schedule_description.schedule.state.paused,
|
|
236
|
+
"downstream_pipeline": poll_info.get("downstream_pipeline"),
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async def pause_polling(self, endpoint_id: str) -> bool:
|
|
240
|
+
"""
|
|
241
|
+
Pause polling for an endpoint (without deleting the schedule).
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
endpoint_id: The endpoint ID to pause
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
True if paused successfully, False if not found
|
|
248
|
+
|
|
249
|
+
Raises:
|
|
250
|
+
RuntimeError: If Temporal client is not available
|
|
251
|
+
"""
|
|
252
|
+
if endpoint_id not in self._active_polls:
|
|
253
|
+
return False
|
|
254
|
+
|
|
255
|
+
if self._temporal_client is None:
|
|
256
|
+
raise RuntimeError("Temporal client not available")
|
|
257
|
+
|
|
258
|
+
poll_info = self._active_polls[endpoint_id]
|
|
259
|
+
schedule_id = poll_info["schedule_id"]
|
|
260
|
+
|
|
261
|
+
schedule_handle = self._temporal_client.get_schedule_handle(schedule_id)
|
|
262
|
+
await schedule_handle.pause()
|
|
263
|
+
|
|
264
|
+
return True
|
|
265
|
+
|
|
266
|
+
async def resume_polling(self, endpoint_id: str) -> bool:
|
|
267
|
+
"""
|
|
268
|
+
Resume a paused polling operation.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
endpoint_id: The endpoint ID to resume
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
True if resumed successfully, False if not found
|
|
275
|
+
|
|
276
|
+
Raises:
|
|
277
|
+
RuntimeError: If Temporal client is not available
|
|
278
|
+
"""
|
|
279
|
+
if endpoint_id not in self._active_polls:
|
|
280
|
+
return False
|
|
281
|
+
|
|
282
|
+
if self._temporal_client is None:
|
|
283
|
+
raise RuntimeError("Temporal client not available")
|
|
284
|
+
|
|
285
|
+
poll_info = self._active_polls[endpoint_id]
|
|
286
|
+
schedule_id = poll_info["schedule_id"]
|
|
287
|
+
|
|
288
|
+
schedule_handle = self._temporal_client.get_schedule_handle(schedule_id)
|
|
289
|
+
await schedule_handle.unpause()
|
|
290
|
+
|
|
291
|
+
return True
|
|
@@ -16,7 +16,7 @@ maintaining proper dependency direction (contrib imports from core, not vice ver
|
|
|
16
16
|
|
|
17
17
|
from julee.util.temporal.decorators import temporal_workflow_proxy
|
|
18
18
|
|
|
19
|
-
from ...domain.services import PollerService
|
|
19
|
+
from ...domain.services.poller import PollerService
|
|
20
20
|
from .activity_names import POLLING_SERVICE_ACTIVITY_BASE
|
|
21
21
|
|
|
22
22
|
|