durabletask 1.3.0.dev27__tar.gz → 1.3.0.dev28__tar.gz
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.
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/PKG-INFO +1 -1
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/__init__.py +12 -1
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/testing/in_memory_backend.py +116 -13
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/worker.py +165 -1
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask.egg-info/PKG-INFO +1 -1
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/pyproject.toml +1 -1
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/LICENSE +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/README.md +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/client.py +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/entities/__init__.py +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/entities/durable_entity.py +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/entities/entity_context.py +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/entities/entity_instance_id.py +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/entities/entity_lock.py +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/entities/entity_metadata.py +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/entities/entity_operation_failed_exception.py +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/extensions/__init__.py +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/extensions/azure_blob_payloads/__init__.py +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/extensions/azure_blob_payloads/blob_payload_store.py +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/extensions/azure_blob_payloads/options.py +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/internal/client_helpers.py +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/internal/entity_state_shim.py +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/internal/exceptions.py +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/internal/grpc_interceptor.py +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/internal/helpers.py +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/internal/json_encode_output_exception.py +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/internal/orchestration_entity_context.py +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/internal/orchestrator_service_pb2.py +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/internal/orchestrator_service_pb2.pyi +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/internal/orchestrator_service_pb2_grpc.py +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/internal/proto_task_hub_sidecar_service_stub.py +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/internal/shared.py +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/internal/tracing.py +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/payload/__init__.py +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/payload/helpers.py +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/payload/store.py +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/py.typed +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/task.py +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/testing/__init__.py +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask.egg-info/SOURCES.txt +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask.egg-info/dependency_links.txt +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask.egg-info/requires.txt +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask.egg-info/top_level.txt +0 -0
- {durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/setup.cfg +0 -0
|
@@ -4,13 +4,24 @@
|
|
|
4
4
|
"""Durable Task SDK for Python"""
|
|
5
5
|
|
|
6
6
|
from durabletask.payload.store import LargePayloadStorageOptions, PayloadStore
|
|
7
|
-
from durabletask.worker import
|
|
7
|
+
from durabletask.worker import (
|
|
8
|
+
ActivityWorkItemFilter,
|
|
9
|
+
ConcurrencyOptions,
|
|
10
|
+
EntityWorkItemFilter,
|
|
11
|
+
OrchestrationWorkItemFilter,
|
|
12
|
+
VersioningOptions,
|
|
13
|
+
WorkItemFilters,
|
|
14
|
+
)
|
|
8
15
|
|
|
9
16
|
__all__ = [
|
|
17
|
+
"ActivityWorkItemFilter",
|
|
10
18
|
"ConcurrencyOptions",
|
|
19
|
+
"EntityWorkItemFilter",
|
|
11
20
|
"LargePayloadStorageOptions",
|
|
21
|
+
"OrchestrationWorkItemFilter",
|
|
12
22
|
"PayloadStore",
|
|
13
23
|
"VersioningOptions",
|
|
24
|
+
"WorkItemFilters",
|
|
14
25
|
]
|
|
15
26
|
|
|
16
27
|
PACKAGE_NAME = "durabletask"
|
{durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/testing/in_memory_backend.py
RENAMED
|
@@ -26,6 +26,7 @@ from google.protobuf import empty_pb2, timestamp_pb2, wrappers_pb2
|
|
|
26
26
|
import durabletask.internal.orchestrator_service_pb2 as pb
|
|
27
27
|
import durabletask.internal.orchestrator_service_pb2_grpc as stubs
|
|
28
28
|
import durabletask.internal.helpers as helpers
|
|
29
|
+
from durabletask.entities.entity_instance_id import EntityInstanceId
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
@dataclass
|
|
@@ -56,6 +57,7 @@ class ActivityWorkItem:
|
|
|
56
57
|
task_id: int
|
|
57
58
|
input: Optional[str]
|
|
58
59
|
completion_token: int
|
|
60
|
+
version: Optional[str] = None
|
|
59
61
|
|
|
60
62
|
|
|
61
63
|
@dataclass
|
|
@@ -451,9 +453,57 @@ class InMemoryOrchestrationBackend(stubs.TaskHubSidecarServiceServicer):
|
|
|
451
453
|
f"Restarted instance '{request.instanceId}' as '{new_instance_id}'")
|
|
452
454
|
return pb.RestartInstanceResponse(instanceId=new_instance_id)
|
|
453
455
|
|
|
456
|
+
@staticmethod
|
|
457
|
+
def _parse_work_item_filters(request: pb.GetWorkItemsRequest):
|
|
458
|
+
"""Extract filters from the request.
|
|
459
|
+
|
|
460
|
+
Returns a tuple of three values, one per work-item category. Each
|
|
461
|
+
value is either ``None`` (no filtering -- dispatch everything) or a
|
|
462
|
+
``dict`` mapping a task name to a ``frozenset`` of accepted versions
|
|
463
|
+
(empty frozenset means *any* version of that name is accepted).
|
|
464
|
+
An empty ``dict`` means the worker opted into filtering for that
|
|
465
|
+
category but listed no names, so *nothing* should match.
|
|
466
|
+
"""
|
|
467
|
+
if not request.HasField("workItemFilters"):
|
|
468
|
+
return None, None, None
|
|
469
|
+
wf = request.workItemFilters
|
|
470
|
+
|
|
471
|
+
def _build_filter(filters):
|
|
472
|
+
result: dict[str, frozenset[str]] = {}
|
|
473
|
+
for f in filters:
|
|
474
|
+
versions = frozenset(f.versions) if f.versions else frozenset()
|
|
475
|
+
existing = result.get(f.name, frozenset())
|
|
476
|
+
result[f.name] = existing | versions
|
|
477
|
+
return result
|
|
478
|
+
|
|
479
|
+
orch_filter = _build_filter(wf.orchestrations)
|
|
480
|
+
activity_filter = _build_filter(wf.activities)
|
|
481
|
+
entity_filter = {f.name: frozenset() for f in wf.entities}
|
|
482
|
+
return orch_filter, activity_filter, entity_filter
|
|
483
|
+
|
|
484
|
+
@staticmethod
|
|
485
|
+
def _matches_filter(name: str, version: Optional[str],
|
|
486
|
+
filt: Optional[dict[str, frozenset[str]]]) -> bool:
|
|
487
|
+
"""Check whether a work item matches the parsed filter.
|
|
488
|
+
|
|
489
|
+
*filt* is ``None`` when the worker did not opt into filtering
|
|
490
|
+
(everything matches). Otherwise it is a dict mapping accepted
|
|
491
|
+
names to a frozenset of accepted versions. An empty frozenset
|
|
492
|
+
means any version of that name is accepted.
|
|
493
|
+
"""
|
|
494
|
+
if filt is None:
|
|
495
|
+
return True
|
|
496
|
+
accepted_versions = filt.get(name)
|
|
497
|
+
if accepted_versions is None:
|
|
498
|
+
return False
|
|
499
|
+
if not accepted_versions:
|
|
500
|
+
return True # empty set -- any version
|
|
501
|
+
return (version or "") in accepted_versions
|
|
502
|
+
|
|
454
503
|
def GetWorkItems(self, request: pb.GetWorkItemsRequest, context):
|
|
455
504
|
"""Streams work items to the worker (orchestration and activity work items)."""
|
|
456
505
|
self._logger.info("Worker connected and requesting work items")
|
|
506
|
+
orch_filter, activity_filter, entity_filter = self._parse_work_item_filters(request)
|
|
457
507
|
|
|
458
508
|
try:
|
|
459
509
|
while context.is_active() and not self._shutdown_event.is_set():
|
|
@@ -461,6 +511,7 @@ class InMemoryOrchestrationBackend(stubs.TaskHubSidecarServiceServicer):
|
|
|
461
511
|
|
|
462
512
|
with self._lock:
|
|
463
513
|
# Check for orchestration work
|
|
514
|
+
skipped_orchs: list[str] = []
|
|
464
515
|
while self._orchestration_queue:
|
|
465
516
|
instance_id = self._orchestration_queue.popleft()
|
|
466
517
|
self._orchestration_queue_set.discard(instance_id)
|
|
@@ -469,11 +520,15 @@ class InMemoryOrchestrationBackend(stubs.TaskHubSidecarServiceServicer):
|
|
|
469
520
|
if not instance or not instance.pending_events:
|
|
470
521
|
continue
|
|
471
522
|
|
|
523
|
+
# Skip if orchestration doesn't match filters
|
|
524
|
+
if not self._matches_filter(
|
|
525
|
+
instance.name, instance.version, orch_filter):
|
|
526
|
+
skipped_orchs.append(instance_id)
|
|
527
|
+
continue
|
|
528
|
+
|
|
472
529
|
if instance_id in self._orchestration_in_flight:
|
|
473
530
|
# Already being processed — re-add to queue
|
|
474
|
-
|
|
475
|
-
self._orchestration_queue.append(instance_id)
|
|
476
|
-
self._orchestration_queue_set.add(instance_id)
|
|
531
|
+
skipped_orchs.append(instance_id)
|
|
477
532
|
break
|
|
478
533
|
|
|
479
534
|
# Move pending events to dispatched_events
|
|
@@ -500,27 +555,66 @@ class InMemoryOrchestrationBackend(stubs.TaskHubSidecarServiceServicer):
|
|
|
500
555
|
)
|
|
501
556
|
break
|
|
502
557
|
|
|
558
|
+
# Re-queue skipped orchestrations for other workers
|
|
559
|
+
for s in skipped_orchs:
|
|
560
|
+
if s not in self._orchestration_queue_set:
|
|
561
|
+
self._orchestration_queue.append(s)
|
|
562
|
+
self._orchestration_queue_set.add(s)
|
|
563
|
+
|
|
503
564
|
# Check for activity work
|
|
504
565
|
if not work_item and self._activity_queue:
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
566
|
+
# Scan for the first matching activity
|
|
567
|
+
skipped: list = []
|
|
568
|
+
matched_activity = None
|
|
569
|
+
while self._activity_queue:
|
|
570
|
+
candidate = self._activity_queue.popleft()
|
|
571
|
+
if not self._matches_filter(
|
|
572
|
+
candidate.name, candidate.version,
|
|
573
|
+
activity_filter):
|
|
574
|
+
skipped.append(candidate)
|
|
575
|
+
continue
|
|
576
|
+
matched_activity = candidate
|
|
577
|
+
break
|
|
578
|
+
# Put back non-matching items
|
|
579
|
+
for s in skipped:
|
|
580
|
+
self._activity_queue.append(s)
|
|
581
|
+
|
|
582
|
+
if matched_activity is not None:
|
|
583
|
+
work_item = pb.WorkItem(
|
|
584
|
+
completionToken=str(matched_activity.completion_token),
|
|
585
|
+
activityRequest=pb.ActivityRequest(
|
|
586
|
+
name=matched_activity.name,
|
|
587
|
+
taskId=matched_activity.task_id,
|
|
588
|
+
input=wrappers_pb2.StringValue(value=matched_activity.input) if matched_activity.input else None,
|
|
589
|
+
orchestrationInstance=pb.OrchestrationInstance(instanceId=matched_activity.instance_id)
|
|
590
|
+
)
|
|
513
591
|
)
|
|
514
|
-
)
|
|
515
592
|
|
|
516
593
|
# Check for entity work
|
|
517
594
|
if not work_item:
|
|
595
|
+
skipped_entities: list[str] = []
|
|
518
596
|
while self._entity_queue:
|
|
519
597
|
entity_id = self._entity_queue.popleft()
|
|
520
598
|
self._entity_queue_set.discard(entity_id)
|
|
521
599
|
entity = self._entities.get(entity_id)
|
|
522
600
|
|
|
523
601
|
if entity and entity.pending_operations:
|
|
602
|
+
# Skip if entity name doesn't match filters
|
|
603
|
+
if entity_filter is not None:
|
|
604
|
+
try:
|
|
605
|
+
parsed = EntityInstanceId.parse(entity_id)
|
|
606
|
+
if not self._matches_filter(
|
|
607
|
+
parsed.entity, None,
|
|
608
|
+
entity_filter):
|
|
609
|
+
skipped_entities.append(entity_id)
|
|
610
|
+
continue
|
|
611
|
+
except ValueError:
|
|
612
|
+
self._logger.warning(
|
|
613
|
+
f"Cannot parse entity ID '{entity_id}' "
|
|
614
|
+
f"for filter matching; skipping")
|
|
615
|
+
skipped_entities.append(entity_id)
|
|
616
|
+
continue
|
|
617
|
+
|
|
524
618
|
# Skip if this entity is already being processed
|
|
525
619
|
if entity_id in self._entity_in_flight:
|
|
526
620
|
continue
|
|
@@ -547,6 +641,12 @@ class InMemoryOrchestrationBackend(stubs.TaskHubSidecarServiceServicer):
|
|
|
547
641
|
)
|
|
548
642
|
break
|
|
549
643
|
|
|
644
|
+
# Re-queue skipped entities for other workers
|
|
645
|
+
for s in skipped_entities:
|
|
646
|
+
if s not in self._entity_queue_set:
|
|
647
|
+
self._entity_queue.append(s)
|
|
648
|
+
self._entity_queue_set.add(s)
|
|
649
|
+
|
|
550
650
|
if work_item:
|
|
551
651
|
yield work_item
|
|
552
652
|
else:
|
|
@@ -1274,12 +1374,15 @@ class InMemoryOrchestrationBackend(stubs.TaskHubSidecarServiceServicer):
|
|
|
1274
1374
|
instance.status = pb.ORCHESTRATION_STATUS_RUNNING
|
|
1275
1375
|
|
|
1276
1376
|
# Queue activity for execution
|
|
1377
|
+
task_version = schedule_task.version.value \
|
|
1378
|
+
if schedule_task.HasField("version") else None
|
|
1277
1379
|
self._activity_queue.append(ActivityWorkItem(
|
|
1278
1380
|
instance_id=instance.instance_id,
|
|
1279
1381
|
name=task_name,
|
|
1280
1382
|
task_id=task_id,
|
|
1281
1383
|
input=input_value,
|
|
1282
|
-
completion_token=instance.completion_token
|
|
1384
|
+
completion_token=instance.completion_token,
|
|
1385
|
+
version=task_version,
|
|
1283
1386
|
))
|
|
1284
1387
|
self._work_available.set()
|
|
1285
1388
|
|
|
@@ -9,11 +9,12 @@ import os
|
|
|
9
9
|
import random
|
|
10
10
|
import time
|
|
11
11
|
from concurrent.futures import ThreadPoolExecutor
|
|
12
|
+
from dataclasses import dataclass, field
|
|
12
13
|
from datetime import datetime, timedelta, timezone
|
|
13
14
|
from threading import Event, Thread
|
|
14
15
|
from types import GeneratorType
|
|
15
16
|
from enum import Enum
|
|
16
|
-
from typing import Any, Generator, Optional, Sequence, Tuple, TypeVar, Union
|
|
17
|
+
from typing import Any, Generator, Optional, Sequence, Tuple, TypeVar, Union, overload
|
|
17
18
|
import uuid
|
|
18
19
|
from packaging.version import InvalidVersion, parse
|
|
19
20
|
|
|
@@ -143,6 +144,109 @@ class VersioningOptions:
|
|
|
143
144
|
self.failure_strategy = failure_strategy
|
|
144
145
|
|
|
145
146
|
|
|
147
|
+
# Sentinel object used to distinguish "auto-generate filters" from "clear filters (None)".
|
|
148
|
+
_AUTO_GENERATE_FILTERS = object()
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@dataclass(frozen=True)
|
|
152
|
+
class OrchestrationWorkItemFilter:
|
|
153
|
+
"""Specifies a filter for orchestration work items."""
|
|
154
|
+
|
|
155
|
+
name: str
|
|
156
|
+
"""The name of the orchestration to filter."""
|
|
157
|
+
versions: list[str] = field(default_factory=list)
|
|
158
|
+
"""Optional list of versions to filter."""
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@dataclass(frozen=True)
|
|
162
|
+
class ActivityWorkItemFilter:
|
|
163
|
+
"""Specifies a filter for activity work items."""
|
|
164
|
+
|
|
165
|
+
name: str
|
|
166
|
+
"""The name of the activity to filter."""
|
|
167
|
+
versions: list[str] = field(default_factory=list)
|
|
168
|
+
"""Optional list of versions to filter."""
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@dataclass(frozen=True)
|
|
172
|
+
class EntityWorkItemFilter:
|
|
173
|
+
"""Specifies a filter for entity work items.
|
|
174
|
+
|
|
175
|
+
The name is normalized to lowercase to match entity registration
|
|
176
|
+
and instance ID conventions.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
name: str
|
|
180
|
+
"""The name of the entity to filter."""
|
|
181
|
+
|
|
182
|
+
def __post_init__(self):
|
|
183
|
+
EntityInstanceId.validate_entity_name(self.name)
|
|
184
|
+
object.__setattr__(self, 'name', self.name.lower())
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@dataclass(frozen=True)
|
|
188
|
+
class WorkItemFilters:
|
|
189
|
+
"""Work item filters for a Durable Task Worker.
|
|
190
|
+
|
|
191
|
+
These filters are passed to the backend and only work items matching the
|
|
192
|
+
filters will be processed by the worker. If no filters are provided, the
|
|
193
|
+
worker will process all work items.
|
|
194
|
+
|
|
195
|
+
By default, no filters are applied. Call
|
|
196
|
+
:meth:`TaskHubGrpcWorker.use_work_item_filters` to enable filtering.
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
orchestrations: list[OrchestrationWorkItemFilter] = field(default_factory=list)
|
|
200
|
+
"""List of orchestration filters."""
|
|
201
|
+
activities: list[ActivityWorkItemFilter] = field(default_factory=list)
|
|
202
|
+
"""List of activity filters."""
|
|
203
|
+
entities: list[EntityWorkItemFilter] = field(default_factory=list)
|
|
204
|
+
"""List of entity filters."""
|
|
205
|
+
|
|
206
|
+
@classmethod
|
|
207
|
+
def _from_registry(cls, registry: '_Registry') -> 'WorkItemFilters':
|
|
208
|
+
"""Auto-generate work item filters from the task registry."""
|
|
209
|
+
versions: list[str] = []
|
|
210
|
+
v = registry.versioning
|
|
211
|
+
if v and v.match_strategy == VersionMatchStrategy.STRICT and v.version:
|
|
212
|
+
versions = [registry.versioning.version]
|
|
213
|
+
|
|
214
|
+
orchestrations = [
|
|
215
|
+
OrchestrationWorkItemFilter(name=name, versions=list(versions))
|
|
216
|
+
for name in registry.orchestrators
|
|
217
|
+
]
|
|
218
|
+
activities = [
|
|
219
|
+
ActivityWorkItemFilter(name=name, versions=list(versions))
|
|
220
|
+
for name in registry.activities
|
|
221
|
+
]
|
|
222
|
+
entities = [
|
|
223
|
+
EntityWorkItemFilter(name=name)
|
|
224
|
+
for name in registry.entities
|
|
225
|
+
]
|
|
226
|
+
return cls(
|
|
227
|
+
orchestrations=orchestrations,
|
|
228
|
+
activities=activities,
|
|
229
|
+
entities=entities,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
def _to_grpc(self) -> pb.WorkItemFilters:
|
|
233
|
+
"""Convert to a gRPC WorkItemFilters message."""
|
|
234
|
+
grpc_filters = pb.WorkItemFilters()
|
|
235
|
+
for f in self.orchestrations:
|
|
236
|
+
grpc_filters.orchestrations.append(
|
|
237
|
+
pb.OrchestrationFilter(name=f.name, versions=f.versions)
|
|
238
|
+
)
|
|
239
|
+
for f in self.activities:
|
|
240
|
+
grpc_filters.activities.append(
|
|
241
|
+
pb.ActivityFilter(name=f.name, versions=f.versions)
|
|
242
|
+
)
|
|
243
|
+
for f in self.entities:
|
|
244
|
+
grpc_filters.entities.append(
|
|
245
|
+
pb.EntityFilter(name=f.name)
|
|
246
|
+
)
|
|
247
|
+
return grpc_filters
|
|
248
|
+
|
|
249
|
+
|
|
146
250
|
class _Registry:
|
|
147
251
|
orchestrators: dict[str, task.Orchestrator]
|
|
148
252
|
activities: dict[str, task.Activity]
|
|
@@ -354,6 +458,8 @@ class TaskHubGrpcWorker:
|
|
|
354
458
|
self._interceptors = None
|
|
355
459
|
|
|
356
460
|
self._async_worker_manager = _AsyncWorkerManager(self._concurrency_options, self._logger)
|
|
461
|
+
self._work_item_filters: Optional[WorkItemFilters] = None
|
|
462
|
+
self._auto_generate_work_item_filters: bool = False
|
|
357
463
|
|
|
358
464
|
@property
|
|
359
465
|
def concurrency_options(self) -> ConcurrencyOptions:
|
|
@@ -396,11 +502,65 @@ class TaskHubGrpcWorker:
|
|
|
396
502
|
raise RuntimeError("Cannot set default version while the worker is running.")
|
|
397
503
|
self._registry.versioning = version
|
|
398
504
|
|
|
505
|
+
@overload
|
|
506
|
+
def use_work_item_filters(self) -> None:
|
|
507
|
+
...
|
|
508
|
+
|
|
509
|
+
@overload
|
|
510
|
+
def use_work_item_filters(self, filters: WorkItemFilters) -> None:
|
|
511
|
+
...
|
|
512
|
+
|
|
513
|
+
@overload
|
|
514
|
+
def use_work_item_filters(self, filters: None) -> None:
|
|
515
|
+
...
|
|
516
|
+
|
|
517
|
+
def use_work_item_filters(
|
|
518
|
+
self,
|
|
519
|
+
filters: Union[WorkItemFilters, None, object] = _AUTO_GENERATE_FILTERS,
|
|
520
|
+
) -> None:
|
|
521
|
+
"""Configures work item filters for the worker.
|
|
522
|
+
|
|
523
|
+
Work item filters tell the backend which orchestrations, activities,
|
|
524
|
+
and entities this worker can handle. When enabled, only matching work
|
|
525
|
+
items are dispatched to this worker.
|
|
526
|
+
|
|
527
|
+
By default no filters are applied and the worker processes all work
|
|
528
|
+
items. Calling this method enables filtering.
|
|
529
|
+
|
|
530
|
+
Args:
|
|
531
|
+
filters: The filters to apply. If omitted (default), filters are
|
|
532
|
+
auto-generated from registered orchestrations, activities, and
|
|
533
|
+
entities at :meth:`start` time. Pass a :class:`WorkItemFilters`
|
|
534
|
+
instance to provide explicit filters. Pass ``None`` to clear
|
|
535
|
+
any previously configured filters.
|
|
536
|
+
"""
|
|
537
|
+
if self._is_running:
|
|
538
|
+
raise RuntimeError(
|
|
539
|
+
"Work item filters cannot be changed while the worker is running."
|
|
540
|
+
)
|
|
541
|
+
if filters is _AUTO_GENERATE_FILTERS:
|
|
542
|
+
self._auto_generate_work_item_filters = True
|
|
543
|
+
self._work_item_filters = None
|
|
544
|
+
elif filters is None:
|
|
545
|
+
self._auto_generate_work_item_filters = False
|
|
546
|
+
self._work_item_filters = None
|
|
547
|
+
elif isinstance(filters, WorkItemFilters):
|
|
548
|
+
self._auto_generate_work_item_filters = False
|
|
549
|
+
self._work_item_filters = filters
|
|
550
|
+
else:
|
|
551
|
+
raise TypeError(
|
|
552
|
+
"filters must be a WorkItemFilters instance, None, or omitted."
|
|
553
|
+
)
|
|
554
|
+
|
|
399
555
|
def start(self):
|
|
400
556
|
"""Starts the worker on a background thread and begins listening for work items."""
|
|
401
557
|
if self._is_running:
|
|
402
558
|
raise RuntimeError("The worker is already running.")
|
|
403
559
|
|
|
560
|
+
# Auto-generate work item filters from registry if opted in
|
|
561
|
+
if self._auto_generate_work_item_filters:
|
|
562
|
+
self._work_item_filters = WorkItemFilters._from_registry(self._registry)
|
|
563
|
+
|
|
404
564
|
def run_loop():
|
|
405
565
|
loop = asyncio.new_event_loop()
|
|
406
566
|
asyncio.set_event_loop(loop)
|
|
@@ -510,6 +670,10 @@ class TaskHubGrpcWorker:
|
|
|
510
670
|
maxConcurrentActivityWorkItems=self._concurrency_options.maximum_concurrent_activity_work_items,
|
|
511
671
|
capabilities=capabilities,
|
|
512
672
|
)
|
|
673
|
+
if self._work_item_filters is not None:
|
|
674
|
+
get_work_items_request.workItemFilters.CopyFrom(
|
|
675
|
+
self._work_item_filters._to_grpc()
|
|
676
|
+
)
|
|
513
677
|
self._response_stream = stub.GetWorkItems(get_work_items_request)
|
|
514
678
|
self._logger.info(
|
|
515
679
|
f"Successfully connected to {self._host_address}. Waiting for work items..."
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/entities/entity_instance_id.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/internal/entity_state_shim.py
RENAMED
|
File without changes
|
|
File without changes
|
{durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/internal/grpc_interceptor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask/internal/orchestrator_service_pb2.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durabletask-1.3.0.dev27 → durabletask-1.3.0.dev28}/durabletask.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|