durabletask 1.1.0.dev4__tar.gz → 1.1.0.dev6__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.1.0.dev4 → durabletask-1.1.0.dev6}/PKG-INFO +1 -1
- {durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/durabletask/internal/helpers.py +17 -0
- durabletask-1.1.0.dev6/durabletask/internal/proto_task_hub_sidecar_service_stub.py +33 -0
- {durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/durabletask/worker.py +94 -32
- {durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/durabletask.egg-info/PKG-INFO +1 -1
- {durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/durabletask.egg-info/SOURCES.txt +1 -0
- {durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/pyproject.toml +1 -1
- {durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/LICENSE +0 -0
- {durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/README.md +0 -0
- {durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/durabletask/__init__.py +0 -0
- {durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/durabletask/client.py +0 -0
- {durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/durabletask/entities/__init__.py +0 -0
- {durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/durabletask/entities/durable_entity.py +0 -0
- {durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/durabletask/entities/entity_context.py +0 -0
- {durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/durabletask/entities/entity_instance_id.py +0 -0
- {durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/durabletask/entities/entity_lock.py +0 -0
- {durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/durabletask/entities/entity_metadata.py +0 -0
- {durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/durabletask/internal/entity_state_shim.py +0 -0
- {durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/durabletask/internal/exceptions.py +0 -0
- {durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/durabletask/internal/grpc_interceptor.py +0 -0
- {durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/durabletask/internal/orchestration_entity_context.py +0 -0
- {durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/durabletask/internal/orchestrator_service_pb2.py +0 -0
- {durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/durabletask/internal/orchestrator_service_pb2.pyi +0 -0
- {durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/durabletask/internal/orchestrator_service_pb2_grpc.py +0 -0
- {durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/durabletask/internal/shared.py +0 -0
- {durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/durabletask/py.typed +0 -0
- {durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/durabletask/task.py +0 -0
- {durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/durabletask.egg-info/dependency_links.txt +0 -0
- {durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/durabletask.egg-info/requires.txt +0 -0
- {durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/durabletask.egg-info/top_level.txt +0 -0
- {durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/setup.cfg +0 -0
|
@@ -20,6 +20,11 @@ def new_orchestrator_started_event(timestamp: Optional[datetime] = None) -> pb.H
|
|
|
20
20
|
return pb.HistoryEvent(eventId=-1, timestamp=ts, orchestratorStarted=pb.OrchestratorStartedEvent())
|
|
21
21
|
|
|
22
22
|
|
|
23
|
+
def new_orchestrator_completed_event() -> pb.HistoryEvent:
|
|
24
|
+
return pb.HistoryEvent(eventId=-1, timestamp=timestamp_pb2.Timestamp(),
|
|
25
|
+
orchestratorCompleted=pb.OrchestratorCompletedEvent())
|
|
26
|
+
|
|
27
|
+
|
|
23
28
|
def new_execution_started_event(name: str, instance_id: str, encoded_input: Optional[str] = None,
|
|
24
29
|
tags: Optional[dict[str, str]] = None) -> pb.HistoryEvent:
|
|
25
30
|
return pb.HistoryEvent(
|
|
@@ -119,6 +124,18 @@ def new_failure_details(ex: Exception) -> pb.TaskFailureDetails:
|
|
|
119
124
|
)
|
|
120
125
|
|
|
121
126
|
|
|
127
|
+
def new_event_sent_event(event_id: int, instance_id: str, input: str):
|
|
128
|
+
return pb.HistoryEvent(
|
|
129
|
+
eventId=event_id,
|
|
130
|
+
timestamp=timestamp_pb2.Timestamp(),
|
|
131
|
+
eventSent=pb.EventSentEvent(
|
|
132
|
+
name="",
|
|
133
|
+
input=get_string_value(input),
|
|
134
|
+
instanceId=instance_id
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
122
139
|
def new_event_raised_event(name: str, encoded_input: Optional[str] = None) -> pb.HistoryEvent:
|
|
123
140
|
return pb.HistoryEvent(
|
|
124
141
|
eventId=-1,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from typing import Any, Callable, Protocol
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ProtoTaskHubSidecarServiceStub(Protocol):
|
|
5
|
+
"""A stub class matching the TaskHubSidecarServiceStub generated from the .proto file.
|
|
6
|
+
Allows the use of TaskHubGrpcWorker methods when a real sidecar stub is not available.
|
|
7
|
+
"""
|
|
8
|
+
Hello: Callable[..., Any]
|
|
9
|
+
StartInstance: Callable[..., Any]
|
|
10
|
+
GetInstance: Callable[..., Any]
|
|
11
|
+
RewindInstance: Callable[..., Any]
|
|
12
|
+
WaitForInstanceStart: Callable[..., Any]
|
|
13
|
+
WaitForInstanceCompletion: Callable[..., Any]
|
|
14
|
+
RaiseEvent: Callable[..., Any]
|
|
15
|
+
TerminateInstance: Callable[..., Any]
|
|
16
|
+
SuspendInstance: Callable[..., Any]
|
|
17
|
+
ResumeInstance: Callable[..., Any]
|
|
18
|
+
QueryInstances: Callable[..., Any]
|
|
19
|
+
PurgeInstances: Callable[..., Any]
|
|
20
|
+
GetWorkItems: Callable[..., Any]
|
|
21
|
+
CompleteActivityTask: Callable[..., Any]
|
|
22
|
+
CompleteOrchestratorTask: Callable[..., Any]
|
|
23
|
+
CompleteEntityTask: Callable[..., Any]
|
|
24
|
+
StreamInstanceHistory: Callable[..., Any]
|
|
25
|
+
CreateTaskHub: Callable[..., Any]
|
|
26
|
+
DeleteTaskHub: Callable[..., Any]
|
|
27
|
+
SignalEntity: Callable[..., Any]
|
|
28
|
+
GetEntity: Callable[..., Any]
|
|
29
|
+
QueryEntities: Callable[..., Any]
|
|
30
|
+
CleanEntityStorage: Callable[..., Any]
|
|
31
|
+
AbandonTaskActivityWorkItem: Callable[..., Any]
|
|
32
|
+
AbandonTaskOrchestratorWorkItem: Callable[..., Any]
|
|
33
|
+
AbandonTaskEntityWorkItem: Callable[..., Any]
|
|
@@ -12,7 +12,7 @@ from datetime import datetime, timedelta, timezone
|
|
|
12
12
|
from threading import Event, Thread
|
|
13
13
|
from types import GeneratorType
|
|
14
14
|
from enum import Enum
|
|
15
|
-
from typing import Any, Generator, Optional, Sequence, TypeVar, Union
|
|
15
|
+
from typing import Any, Generator, Optional, Sequence, Tuple, TypeVar, Union
|
|
16
16
|
import uuid
|
|
17
17
|
from packaging.version import InvalidVersion, parse
|
|
18
18
|
|
|
@@ -24,6 +24,7 @@ from durabletask.internal.entity_state_shim import StateShim
|
|
|
24
24
|
from durabletask.internal.helpers import new_timestamp
|
|
25
25
|
from durabletask.entities import DurableEntity, EntityLock, EntityInstanceId, EntityContext
|
|
26
26
|
from durabletask.internal.orchestration_entity_context import OrchestrationEntityContext
|
|
27
|
+
from durabletask.internal.proto_task_hub_sidecar_service_stub import ProtoTaskHubSidecarServiceStub
|
|
27
28
|
import durabletask.internal.helpers as ph
|
|
28
29
|
import durabletask.internal.exceptions as pe
|
|
29
30
|
import durabletask.internal.orchestrator_service_pb2 as pb
|
|
@@ -631,7 +632,7 @@ class TaskHubGrpcWorker:
|
|
|
631
632
|
def _execute_orchestrator(
|
|
632
633
|
self,
|
|
633
634
|
req: pb.OrchestratorRequest,
|
|
634
|
-
stub: stubs.TaskHubSidecarServiceStub,
|
|
635
|
+
stub: Union[stubs.TaskHubSidecarServiceStub, ProtoTaskHubSidecarServiceStub],
|
|
635
636
|
completionToken,
|
|
636
637
|
):
|
|
637
638
|
try:
|
|
@@ -679,7 +680,7 @@ class TaskHubGrpcWorker:
|
|
|
679
680
|
def _cancel_orchestrator(
|
|
680
681
|
self,
|
|
681
682
|
req: pb.OrchestratorRequest,
|
|
682
|
-
stub: stubs.TaskHubSidecarServiceStub,
|
|
683
|
+
stub: Union[stubs.TaskHubSidecarServiceStub, ProtoTaskHubSidecarServiceStub],
|
|
683
684
|
completionToken,
|
|
684
685
|
):
|
|
685
686
|
stub.AbandonTaskOrchestratorWorkItem(
|
|
@@ -692,7 +693,7 @@ class TaskHubGrpcWorker:
|
|
|
692
693
|
def _execute_activity(
|
|
693
694
|
self,
|
|
694
695
|
req: pb.ActivityRequest,
|
|
695
|
-
stub: stubs.TaskHubSidecarServiceStub,
|
|
696
|
+
stub: Union[stubs.TaskHubSidecarServiceStub, ProtoTaskHubSidecarServiceStub],
|
|
696
697
|
completionToken,
|
|
697
698
|
):
|
|
698
699
|
instance_id = req.orchestrationInstance.instanceId
|
|
@@ -725,7 +726,7 @@ class TaskHubGrpcWorker:
|
|
|
725
726
|
def _cancel_activity(
|
|
726
727
|
self,
|
|
727
728
|
req: pb.ActivityRequest,
|
|
728
|
-
stub: stubs.TaskHubSidecarServiceStub,
|
|
729
|
+
stub: Union[stubs.TaskHubSidecarServiceStub, ProtoTaskHubSidecarServiceStub],
|
|
729
730
|
completionToken,
|
|
730
731
|
):
|
|
731
732
|
stub.AbandonTaskActivityWorkItem(
|
|
@@ -738,7 +739,7 @@ class TaskHubGrpcWorker:
|
|
|
738
739
|
def _execute_entity_batch(
|
|
739
740
|
self,
|
|
740
741
|
req: Union[pb.EntityBatchRequest, pb.EntityRequest],
|
|
741
|
-
stub: stubs.TaskHubSidecarServiceStub,
|
|
742
|
+
stub: Union[stubs.TaskHubSidecarServiceStub, ProtoTaskHubSidecarServiceStub],
|
|
742
743
|
completionToken,
|
|
743
744
|
):
|
|
744
745
|
if isinstance(req, pb.EntityRequest):
|
|
@@ -807,7 +808,7 @@ class TaskHubGrpcWorker:
|
|
|
807
808
|
def _cancel_entity_batch(
|
|
808
809
|
self,
|
|
809
810
|
req: Union[pb.EntityBatchRequest, pb.EntityRequest],
|
|
810
|
-
stub: stubs.TaskHubSidecarServiceStub,
|
|
811
|
+
stub: Union[stubs.TaskHubSidecarServiceStub, ProtoTaskHubSidecarServiceStub],
|
|
811
812
|
completionToken,
|
|
812
813
|
):
|
|
813
814
|
stub.AbandonTaskEntityWorkItem(
|
|
@@ -831,6 +832,7 @@ class _RuntimeOrchestrationContext(task.OrchestrationContext):
|
|
|
831
832
|
self._pending_tasks: dict[int, task.CompletableTask] = {}
|
|
832
833
|
# Maps entity ID to task ID
|
|
833
834
|
self._entity_task_id_map: dict[str, tuple[EntityInstanceId, int]] = {}
|
|
835
|
+
self._entity_lock_task_id_map: dict[str, tuple[EntityInstanceId, int]] = {}
|
|
834
836
|
# Maps criticalSectionId to task ID
|
|
835
837
|
self._entity_lock_id_map: dict[str, int] = {}
|
|
836
838
|
self._sequence_number = 0
|
|
@@ -1605,33 +1607,40 @@ class _OrchestrationExecutor:
|
|
|
1605
1607
|
else:
|
|
1606
1608
|
raise TypeError("Unexpected sub-orchestration task type")
|
|
1607
1609
|
elif event.HasField("eventRaised"):
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
if task_list:
|
|
1615
|
-
event_task = task_list.pop(0)
|
|
1616
|
-
if not ph.is_empty(event.eventRaised.input):
|
|
1617
|
-
decoded_result = shared.from_json(event.eventRaised.input.value)
|
|
1618
|
-
event_task.complete(decoded_result)
|
|
1619
|
-
if not task_list:
|
|
1620
|
-
del ctx._pending_events[event_name]
|
|
1621
|
-
ctx.resume()
|
|
1610
|
+
if event.eventRaised.name in ctx._entity_task_id_map:
|
|
1611
|
+
entity_id, task_id = ctx._entity_task_id_map.get(event.eventRaised.name, (None, None))
|
|
1612
|
+
self._handle_entity_event_raised(ctx, event, entity_id, task_id, False)
|
|
1613
|
+
elif event.eventRaised.name in ctx._entity_lock_task_id_map:
|
|
1614
|
+
entity_id, task_id = ctx._entity_lock_task_id_map.get(event.eventRaised.name, (None, None))
|
|
1615
|
+
self._handle_entity_event_raised(ctx, event, entity_id, task_id, True)
|
|
1622
1616
|
else:
|
|
1623
|
-
#
|
|
1624
|
-
|
|
1625
|
-
if not event_list:
|
|
1626
|
-
event_list = []
|
|
1627
|
-
ctx._received_events[event_name] = event_list
|
|
1628
|
-
if not ph.is_empty(event.eventRaised.input):
|
|
1629
|
-
decoded_result = shared.from_json(event.eventRaised.input.value)
|
|
1630
|
-
event_list.append(decoded_result)
|
|
1617
|
+
# event names are case-insensitive
|
|
1618
|
+
event_name = event.eventRaised.name.casefold()
|
|
1631
1619
|
if not ctx.is_replaying:
|
|
1632
|
-
self._logger.info(
|
|
1633
|
-
|
|
1634
|
-
|
|
1620
|
+
self._logger.info(f"{ctx.instance_id} Event raised: {event_name}")
|
|
1621
|
+
task_list = ctx._pending_events.get(event_name, None)
|
|
1622
|
+
decoded_result: Optional[Any] = None
|
|
1623
|
+
if task_list:
|
|
1624
|
+
event_task = task_list.pop(0)
|
|
1625
|
+
if not ph.is_empty(event.eventRaised.input):
|
|
1626
|
+
decoded_result = shared.from_json(event.eventRaised.input.value)
|
|
1627
|
+
event_task.complete(decoded_result)
|
|
1628
|
+
if not task_list:
|
|
1629
|
+
del ctx._pending_events[event_name]
|
|
1630
|
+
ctx.resume()
|
|
1631
|
+
else:
|
|
1632
|
+
# buffer the event
|
|
1633
|
+
event_list = ctx._received_events.get(event_name, None)
|
|
1634
|
+
if not event_list:
|
|
1635
|
+
event_list = []
|
|
1636
|
+
ctx._received_events[event_name] = event_list
|
|
1637
|
+
if not ph.is_empty(event.eventRaised.input):
|
|
1638
|
+
decoded_result = shared.from_json(event.eventRaised.input.value)
|
|
1639
|
+
event_list.append(decoded_result)
|
|
1640
|
+
if not ctx.is_replaying:
|
|
1641
|
+
self._logger.info(
|
|
1642
|
+
f"{ctx.instance_id}: Event '{event_name}' has been buffered as there are no tasks waiting for it."
|
|
1643
|
+
)
|
|
1635
1644
|
elif event.HasField("executionSuspended"):
|
|
1636
1645
|
if not self._is_suspended and not ctx.is_replaying:
|
|
1637
1646
|
self._logger.info(f"{ctx.instance_id}: Execution suspended.")
|
|
@@ -1759,6 +1768,21 @@ class _OrchestrationExecutor:
|
|
|
1759
1768
|
self._logger.info(f"{ctx.instance_id}: Entity operation failed.")
|
|
1760
1769
|
self._logger.info(f"Data: {json.dumps(event.entityOperationFailed)}")
|
|
1761
1770
|
pass
|
|
1771
|
+
elif event.HasField("orchestratorCompleted"):
|
|
1772
|
+
# Added in Functions only (for some reason) and does not affect orchestrator flow
|
|
1773
|
+
pass
|
|
1774
|
+
elif event.HasField("eventSent"):
|
|
1775
|
+
# Check if this eventSent corresponds to an entity operation call after being translated to the old
|
|
1776
|
+
# entity protocol by the Durable WebJobs extension. If so, treat this message similarly to
|
|
1777
|
+
# entityOperationCalled and remove the pending action. Also store the entity id and event id for later
|
|
1778
|
+
action = ctx._pending_actions.pop(event.eventId, None)
|
|
1779
|
+
if action and action.HasField("sendEntityMessage"):
|
|
1780
|
+
if action.sendEntityMessage.HasField("entityOperationCalled"):
|
|
1781
|
+
entity_id, event_id = self._parse_entity_event_sent_input(event)
|
|
1782
|
+
ctx._entity_task_id_map[event_id] = (entity_id, event.eventId)
|
|
1783
|
+
elif action.sendEntityMessage.HasField("entityLockRequested"):
|
|
1784
|
+
entity_id, event_id = self._parse_entity_event_sent_input(event)
|
|
1785
|
+
ctx._entity_lock_task_id_map[event_id] = (entity_id, event.eventId)
|
|
1762
1786
|
else:
|
|
1763
1787
|
eventType = event.WhichOneof("eventType")
|
|
1764
1788
|
raise task.OrchestrationStateError(
|
|
@@ -1768,6 +1792,44 @@ class _OrchestrationExecutor:
|
|
|
1768
1792
|
# The orchestrator generator function completed
|
|
1769
1793
|
ctx.set_complete(generatorStopped.value, pb.ORCHESTRATION_STATUS_COMPLETED)
|
|
1770
1794
|
|
|
1795
|
+
def _parse_entity_event_sent_input(self, event: pb.HistoryEvent) -> Tuple[EntityInstanceId, str]:
|
|
1796
|
+
try:
|
|
1797
|
+
entity_id = EntityInstanceId.parse(event.eventSent.instanceId)
|
|
1798
|
+
except ValueError:
|
|
1799
|
+
raise RuntimeError(f"Could not parse entity ID from instanceId '{event.eventSent.instanceId}'")
|
|
1800
|
+
try:
|
|
1801
|
+
event_id = json.loads(event.eventSent.input.value)["id"]
|
|
1802
|
+
except (json.JSONDecodeError, KeyError, TypeError) as ex:
|
|
1803
|
+
raise RuntimeError(f"Could not parse event ID from eventSent input '{event.eventSent.input.value}'") from ex
|
|
1804
|
+
return entity_id, event_id
|
|
1805
|
+
|
|
1806
|
+
def _handle_entity_event_raised(self,
|
|
1807
|
+
ctx: _RuntimeOrchestrationContext,
|
|
1808
|
+
event: pb.HistoryEvent,
|
|
1809
|
+
entity_id: Optional[EntityInstanceId],
|
|
1810
|
+
task_id: Optional[int],
|
|
1811
|
+
is_lock_event: bool):
|
|
1812
|
+
# This eventRaised represents the result of an entity operation after being translated to the old
|
|
1813
|
+
# entity protocol by the Durable WebJobs extension
|
|
1814
|
+
if entity_id is None:
|
|
1815
|
+
raise RuntimeError(f"Could not retrieve entity ID for entity-related eventRaised with ID '{event.eventId}'")
|
|
1816
|
+
if task_id is None:
|
|
1817
|
+
raise RuntimeError(f"Could not retrieve task ID for entity-related eventRaised with ID '{event.eventId}'")
|
|
1818
|
+
entity_task = ctx._pending_tasks.pop(task_id, None)
|
|
1819
|
+
if not entity_task:
|
|
1820
|
+
raise RuntimeError(f"Could not retrieve entity task for entity-related eventRaised with ID '{event.eventId}'")
|
|
1821
|
+
result = None
|
|
1822
|
+
if not ph.is_empty(event.eventRaised.input):
|
|
1823
|
+
# TODO: Investigate why the event result is wrapped in a dict with "result" key
|
|
1824
|
+
result = shared.from_json(event.eventRaised.input.value)["result"]
|
|
1825
|
+
if is_lock_event:
|
|
1826
|
+
ctx._entity_context.complete_acquire(event.eventRaised.name)
|
|
1827
|
+
entity_task.complete(EntityLock(ctx))
|
|
1828
|
+
else:
|
|
1829
|
+
ctx._entity_context.recover_lock_after_call(entity_id)
|
|
1830
|
+
entity_task.complete(result)
|
|
1831
|
+
ctx.resume()
|
|
1832
|
+
|
|
1771
1833
|
def evaluate_orchestration_versioning(self, versioning: Optional[VersioningOptions], orchestration_version: Optional[str]) -> Optional[pb.TaskFailureDetails]:
|
|
1772
1834
|
if versioning is None:
|
|
1773
1835
|
return None
|
|
@@ -25,4 +25,5 @@ durabletask/internal/orchestration_entity_context.py
|
|
|
25
25
|
durabletask/internal/orchestrator_service_pb2.py
|
|
26
26
|
durabletask/internal/orchestrator_service_pb2.pyi
|
|
27
27
|
durabletask/internal/orchestrator_service_pb2_grpc.py
|
|
28
|
+
durabletask/internal/proto_task_hub_sidecar_service_stub.py
|
|
28
29
|
durabletask/internal/shared.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/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
|
{durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/durabletask/internal/orchestrator_service_pb2.py
RENAMED
|
File without changes
|
{durabletask-1.1.0.dev4 → durabletask-1.1.0.dev6}/durabletask/internal/orchestrator_service_pb2.pyi
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
|