nora-lib 0.0.5.dev3__tar.gz → 0.0.6.dev1__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.
- {nora_lib-0.0.5.dev3 → nora_lib-0.0.6.dev1}/PKG-INFO +2 -1
- {nora_lib-0.0.5.dev3 → nora_lib-0.0.6.dev1}/nora_lib/interactions/interactions_service.py +73 -8
- {nora_lib-0.0.5.dev3 → nora_lib-0.0.6.dev1}/nora_lib/interactions/models.py +3 -3
- nora_lib-0.0.6.dev1/nora_lib/tasks/state.py +163 -0
- {nora_lib-0.0.5.dev3 → nora_lib-0.0.6.dev1}/nora_lib.egg-info/PKG-INFO +2 -1
- {nora_lib-0.0.5.dev3 → nora_lib-0.0.6.dev1}/nora_lib.egg-info/requires.txt +1 -0
- {nora_lib-0.0.5.dev3 → nora_lib-0.0.6.dev1}/setup.py +2 -2
- nora_lib-0.0.5.dev3/nora_lib/tasks/state.py +0 -53
- {nora_lib-0.0.5.dev3 → nora_lib-0.0.6.dev1}/README.md +0 -0
- {nora_lib-0.0.5.dev3 → nora_lib-0.0.6.dev1}/nora_lib/__init__.py +0 -0
- {nora_lib-0.0.5.dev3 → nora_lib-0.0.6.dev1}/nora_lib/context/__init__.py +0 -0
- {nora_lib-0.0.5.dev3 → nora_lib-0.0.6.dev1}/nora_lib/context/context_service.py +0 -0
- {nora_lib-0.0.5.dev3 → nora_lib-0.0.6.dev1}/nora_lib/context/models.py +0 -0
- {nora_lib-0.0.5.dev3 → nora_lib-0.0.6.dev1}/nora_lib/interactions/__init__.py +0 -0
- {nora_lib-0.0.5.dev3 → nora_lib-0.0.6.dev1}/nora_lib/py.typed +0 -0
- {nora_lib-0.0.5.dev3 → nora_lib-0.0.6.dev1}/nora_lib/tasks/__init__.py +0 -0
- {nora_lib-0.0.5.dev3 → nora_lib-0.0.6.dev1}/nora_lib/tasks/models.py +0 -0
- {nora_lib-0.0.5.dev3 → nora_lib-0.0.6.dev1}/nora_lib.egg-info/SOURCES.txt +0 -0
- {nora_lib-0.0.5.dev3 → nora_lib-0.0.6.dev1}/nora_lib.egg-info/dependency_links.txt +0 -0
- {nora_lib-0.0.5.dev3 → nora_lib-0.0.6.dev1}/nora_lib.egg-info/top_level.txt +0 -0
- {nora_lib-0.0.5.dev3 → nora_lib-0.0.6.dev1}/pyproject.toml +0 -0
- {nora_lib-0.0.5.dev3 → nora_lib-0.0.6.dev1}/setup.cfg +0 -0
- {nora_lib-0.0.5.dev3 → nora_lib-0.0.6.dev1}/tests/tasks/__init__.py +0 -0
- {nora_lib-0.0.5.dev3 → nora_lib-0.0.6.dev1}/tests/tasks/test_state.py +0 -0
- {nora_lib-0.0.5.dev3 → nora_lib-0.0.6.dev1}/tests/test_placeholder.py +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: nora_lib
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.6.dev1
|
|
4
4
|
Summary: For making and coordinating agents and tools
|
|
5
5
|
Home-page: https://github.com/allenai/nora_lib
|
|
6
6
|
Requires-Python: >=3.9
|
|
7
7
|
Requires-Dist: pydantic<3,>=2
|
|
8
8
|
Requires-Dist: requests
|
|
9
|
+
Requires-Dist: boto3
|
|
9
10
|
Provides-Extra: dev
|
|
10
11
|
Requires-Dist: mypy; extra == "dev"
|
|
11
12
|
Requires-Dist: pytest; extra == "dev"
|
|
@@ -2,6 +2,7 @@ from datetime import datetime
|
|
|
2
2
|
import logging
|
|
3
3
|
import requests
|
|
4
4
|
from typing import List, Optional
|
|
5
|
+
import json
|
|
5
6
|
|
|
6
7
|
from nora_lib.interactions.models import (
|
|
7
8
|
AnnotationBatch,
|
|
@@ -31,7 +32,7 @@ class InteractionsService:
|
|
|
31
32
|
message_url,
|
|
32
33
|
json=message.model_dump(),
|
|
33
34
|
headers=self.headers,
|
|
34
|
-
timeout=
|
|
35
|
+
timeout=self.timeout,
|
|
35
36
|
)
|
|
36
37
|
response.raise_for_status()
|
|
37
38
|
|
|
@@ -42,7 +43,7 @@ class InteractionsService:
|
|
|
42
43
|
event_url,
|
|
43
44
|
json=event.model_dump(),
|
|
44
45
|
headers=self.headers,
|
|
45
|
-
timeout=
|
|
46
|
+
timeout=self.timeout,
|
|
46
47
|
)
|
|
47
48
|
response.raise_for_status()
|
|
48
49
|
|
|
@@ -53,7 +54,7 @@ class InteractionsService:
|
|
|
53
54
|
annotation_url,
|
|
54
55
|
json=annotation.model_dump(),
|
|
55
56
|
headers=self.headers,
|
|
56
|
-
timeout=
|
|
57
|
+
timeout=self.timeout,
|
|
57
58
|
)
|
|
58
59
|
response.raise_for_status()
|
|
59
60
|
|
|
@@ -68,7 +69,7 @@ class InteractionsService:
|
|
|
68
69
|
message_url,
|
|
69
70
|
json=request_body,
|
|
70
71
|
headers=self.headers,
|
|
71
|
-
timeout=
|
|
72
|
+
timeout=self.timeout,
|
|
72
73
|
)
|
|
73
74
|
response.raise_for_status()
|
|
74
75
|
res_dict = response.json()["message"]
|
|
@@ -92,7 +93,7 @@ class InteractionsService:
|
|
|
92
93
|
message_url,
|
|
93
94
|
json=request_body,
|
|
94
95
|
headers=self.headers,
|
|
95
|
-
timeout=
|
|
96
|
+
timeout=self.timeout,
|
|
96
97
|
)
|
|
97
98
|
response.raise_for_status()
|
|
98
99
|
return response.json()
|
|
@@ -143,7 +144,7 @@ class InteractionsService:
|
|
|
143
144
|
message_url,
|
|
144
145
|
json=request_body,
|
|
145
146
|
headers=self.headers,
|
|
146
|
-
timeout=
|
|
147
|
+
timeout=self.timeout,
|
|
147
148
|
)
|
|
148
149
|
response.raise_for_status()
|
|
149
150
|
json_response = response.json()
|
|
@@ -176,11 +177,46 @@ class InteractionsService:
|
|
|
176
177
|
thread_search_url,
|
|
177
178
|
json=request_body,
|
|
178
179
|
headers=self.headers,
|
|
179
|
-
timeout=
|
|
180
|
+
timeout=self.timeout,
|
|
180
181
|
)
|
|
181
182
|
response.raise_for_status()
|
|
182
183
|
return response.json()
|
|
183
184
|
|
|
185
|
+
def fetch_messages_and_events_for_forked_thread(
|
|
186
|
+
self, message_id: str, event_type: str
|
|
187
|
+
) -> List[ReturnedMessage]:
|
|
188
|
+
"""Build a history of messages for a given message including associated events.
|
|
189
|
+
This includes messages from pre-forked threads."""
|
|
190
|
+
returned_messages: List[ReturnedMessage] = []
|
|
191
|
+
|
|
192
|
+
messages_for_thread: ThreadRelationsResponse = (
|
|
193
|
+
self.fetch_thread_messages_and_events_for_message(message_id, [event_type])
|
|
194
|
+
)
|
|
195
|
+
if messages_for_thread.messages:
|
|
196
|
+
returned_messages.extend(messages_for_thread.messages)
|
|
197
|
+
|
|
198
|
+
# Lookup any thread_fork events (conversation across surfaces)
|
|
199
|
+
thread_fork_events = self.fetch_messages_and_events_for_thread(
|
|
200
|
+
messages_for_thread.thread_id, EventType.THREAD_FORK.value
|
|
201
|
+
)
|
|
202
|
+
for forked_thread_event in thread_fork_events.get("thread", {}).get(
|
|
203
|
+
"events", []
|
|
204
|
+
):
|
|
205
|
+
event_data = ThreadForkEventData.model_validate(
|
|
206
|
+
forked_thread_event.get("data", {})
|
|
207
|
+
)
|
|
208
|
+
forked_thread: ThreadRelationsResponse = (
|
|
209
|
+
self.fetch_thread_messages_and_events_for_message(
|
|
210
|
+
event_data.previous_message_id, [event_type]
|
|
211
|
+
)
|
|
212
|
+
)
|
|
213
|
+
if forked_thread.messages:
|
|
214
|
+
returned_messages.extend(forked_thread.messages)
|
|
215
|
+
|
|
216
|
+
returned_messages.sort(key=lambda x: x.ts)
|
|
217
|
+
|
|
218
|
+
return returned_messages
|
|
219
|
+
|
|
184
220
|
def fetch_events_for_message(
|
|
185
221
|
self,
|
|
186
222
|
message_id: str,
|
|
@@ -199,7 +235,7 @@ class InteractionsService:
|
|
|
199
235
|
message_search_url,
|
|
200
236
|
json=request_body,
|
|
201
237
|
headers=self.headers,
|
|
202
|
-
timeout=
|
|
238
|
+
timeout=self.timeout,
|
|
203
239
|
)
|
|
204
240
|
response.raise_for_status()
|
|
205
241
|
return response.json()
|
|
@@ -237,3 +273,32 @@ class InteractionsService:
|
|
|
237
273
|
}
|
|
238
274
|
},
|
|
239
275
|
}
|
|
276
|
+
|
|
277
|
+
@staticmethod
|
|
278
|
+
def prod() -> "InteractionsService":
|
|
279
|
+
return InteractionsService(
|
|
280
|
+
base_url="https://s2ub.prod.s2.allenai.org/service/noraretrieval",
|
|
281
|
+
timeout=30,
|
|
282
|
+
token=InteractionsService._fetch_bearer_token(
|
|
283
|
+
"nora/prod/interaction-bearer-token"
|
|
284
|
+
),
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
@staticmethod
|
|
288
|
+
def dev() -> "InteractionsService":
|
|
289
|
+
return InteractionsService(
|
|
290
|
+
base_url="https://s2ub.dev.s2.allenai.org/service/noraretrieval",
|
|
291
|
+
timeout=30,
|
|
292
|
+
token=InteractionsService._fetch_bearer_token(
|
|
293
|
+
"nora/dev/interaction-bearer-token"
|
|
294
|
+
),
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
@staticmethod
|
|
298
|
+
def _fetch_bearer_token(secret_id: str) -> str:
|
|
299
|
+
import boto3
|
|
300
|
+
|
|
301
|
+
secrets_manager = boto3.client("secretsmanager", region_name="us-west-2")
|
|
302
|
+
return json.loads(
|
|
303
|
+
secrets_manager.get_secret_value(SecretId=secret_id)["SecretString"]
|
|
304
|
+
)["token"]
|
|
@@ -43,7 +43,7 @@ class Message(BaseModel):
|
|
|
43
43
|
channel_id: str
|
|
44
44
|
surface: Surface
|
|
45
45
|
ts: datetime
|
|
46
|
-
annotations:
|
|
46
|
+
annotations: List[Annotation] = Field(default_factory=list)
|
|
47
47
|
|
|
48
48
|
@field_serializer("actor_id")
|
|
49
49
|
def serialize_actor_id(self, actor_id: UUID):
|
|
@@ -97,7 +97,7 @@ class ReturnedMessage(BaseModel):
|
|
|
97
97
|
events: List[Event] = Field(default_factory=list)
|
|
98
98
|
thread_id: Optional[str] = None
|
|
99
99
|
channel_id: Optional[str] = None
|
|
100
|
-
annotations:
|
|
100
|
+
annotations: List[Annotation] = Field(default_factory=list)
|
|
101
101
|
|
|
102
102
|
|
|
103
103
|
class AgentMessageData(BaseModel):
|
|
@@ -127,7 +127,7 @@ class ReturnedAgentContextMessage(BaseModel):
|
|
|
127
127
|
text: str
|
|
128
128
|
ts: str
|
|
129
129
|
annotated_text: Optional[str] = None
|
|
130
|
-
events:
|
|
130
|
+
events: List[ReturnedAgentContextEvent] = Field(default_factory=list)
|
|
131
131
|
|
|
132
132
|
|
|
133
133
|
class ThreadForkEventData(BaseModel):
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Handles IO for asynchronous task-related state.
|
|
3
|
+
|
|
4
|
+
A StateManager instance must be initialized with
|
|
5
|
+
a concrete subclass of `AsyncTaskState`, as implemented
|
|
6
|
+
by dependent projects.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
from uuid import UUID
|
|
12
|
+
from datetime import datetime, timezone
|
|
13
|
+
from typing import Generic, Optional, Type, Any
|
|
14
|
+
from abc import ABC, abstractmethod
|
|
15
|
+
|
|
16
|
+
from nora_lib.tasks.models import AsyncTaskState, R, TASK_STATUSES
|
|
17
|
+
from nora_lib.interactions.interactions_service import InteractionsService
|
|
18
|
+
from nora_lib.interactions.models import Event
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class NoSuchTaskException(Exception):
|
|
22
|
+
def __init__(self, task_id: str):
|
|
23
|
+
self._task_id = task_id
|
|
24
|
+
|
|
25
|
+
def __str__(self):
|
|
26
|
+
return f"No record found for task {self._task_id}"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class IStateManager(ABC, Generic[R]):
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def read_state(self, task_id: str) -> AsyncTaskState[R]:
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def write_state(self, state: AsyncTaskState[R]) -> None:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
def update_status(self, task_id: str, new_status: str) -> None:
|
|
39
|
+
state = self.read_state(task_id)
|
|
40
|
+
state.task_status = new_status
|
|
41
|
+
self.write_state(state)
|
|
42
|
+
|
|
43
|
+
def save_result(self, task_id: str, task_result: R) -> None:
|
|
44
|
+
state = self.read_state(task_id)
|
|
45
|
+
state.task_status = TASK_STATUSES["COMPLETED"]
|
|
46
|
+
state.task_result = task_result
|
|
47
|
+
self.write_state(state)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class StateManager(IStateManager[R]):
|
|
51
|
+
"""
|
|
52
|
+
Stores task state on local disk
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(self, task_state_class: Type[AsyncTaskState[R]], state_dir) -> None:
|
|
56
|
+
self._task_state_class = task_state_class
|
|
57
|
+
self._state_dir = state_dir
|
|
58
|
+
|
|
59
|
+
def read_state(self, task_id: str) -> AsyncTaskState[R]:
|
|
60
|
+
task_state_path = os.path.join(self._state_dir, f"{task_id}.json")
|
|
61
|
+
if not os.path.isfile(task_state_path):
|
|
62
|
+
raise NoSuchTaskException(task_id)
|
|
63
|
+
|
|
64
|
+
with open(task_state_path, "r") as f:
|
|
65
|
+
return self._task_state_class(**json.loads(f.read()))
|
|
66
|
+
|
|
67
|
+
def write_state(self, state: AsyncTaskState[R]) -> None:
|
|
68
|
+
task_state_path = os.path.join(self._state_dir, f"{state.task_id}.json")
|
|
69
|
+
with open(task_state_path, "w") as f:
|
|
70
|
+
json.dump(state.model_dump(), f)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class TaskStateFetchException(Exception):
|
|
74
|
+
def __init__(self, message: str):
|
|
75
|
+
super().__init__(message)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class RemoteStateManagerFactory:
|
|
79
|
+
"""
|
|
80
|
+
Stores task state in the interaction store
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
def __init__(
|
|
84
|
+
self, agent_name: str, actor_id: UUID, interactions_service: InteractionsService
|
|
85
|
+
):
|
|
86
|
+
"""
|
|
87
|
+
:param agent_name: Used to form the event type that will hold the task state in the interactions store
|
|
88
|
+
:param actor_id: Associated with the events written to the interactions store
|
|
89
|
+
:param interactions_service:
|
|
90
|
+
"""
|
|
91
|
+
self.agent_name = agent_name
|
|
92
|
+
self.actor_id = actor_id
|
|
93
|
+
self.interactions_service = interactions_service
|
|
94
|
+
|
|
95
|
+
def for_message(self, message_id: str) -> IStateManager[R]:
|
|
96
|
+
return RemoteStateManager(
|
|
97
|
+
self.agent_name, self.actor_id, self.interactions_service, message_id
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class RemoteStateManager(IStateManager[R]):
|
|
102
|
+
"""
|
|
103
|
+
Stores task state in the interaction store
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
_TASK_STATE_EVENT_TYPE = "agent:{}:task_state"
|
|
107
|
+
|
|
108
|
+
def __init__(
|
|
109
|
+
self,
|
|
110
|
+
agent_name: str,
|
|
111
|
+
actor_id: UUID,
|
|
112
|
+
interactions_service: InteractionsService,
|
|
113
|
+
message_id: str,
|
|
114
|
+
):
|
|
115
|
+
"""
|
|
116
|
+
:param agent_name: Agent that saved the task
|
|
117
|
+
:param actor_id: ID for the agent (ignored when reading)
|
|
118
|
+
:param message_id: The message that initiated the request for task status
|
|
119
|
+
"""
|
|
120
|
+
self.agent_name = agent_name
|
|
121
|
+
self.actor_id = actor_id
|
|
122
|
+
self.message_id = message_id
|
|
123
|
+
self.interactions_service = interactions_service
|
|
124
|
+
|
|
125
|
+
def read_state(self, task_id: str) -> AsyncTaskState[R]:
|
|
126
|
+
event_type = RemoteStateManager._TASK_STATE_EVENT_TYPE.format(self.agent_name)
|
|
127
|
+
response = (
|
|
128
|
+
self.interactions_service.fetch_thread_messages_and_events_for_message(
|
|
129
|
+
self.message_id, [event_type]
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
latest_state: Optional[AsyncTaskState[R]] = None
|
|
133
|
+
latest_timestamp = None
|
|
134
|
+
for msg in response.messages or []:
|
|
135
|
+
for event in msg.events or []:
|
|
136
|
+
try:
|
|
137
|
+
state = AsyncTaskState[Any].model_validate(event.data)
|
|
138
|
+
except Exception as e:
|
|
139
|
+
# Event json blob has unexpected format
|
|
140
|
+
raise TaskStateFetchException(
|
|
141
|
+
f"Event of type {event_type} for message {self.message_id} does not deserialize to AsyncTaskState: {e}"
|
|
142
|
+
)
|
|
143
|
+
if state.task_id != task_id:
|
|
144
|
+
continue
|
|
145
|
+
if latest_state is None or (
|
|
146
|
+
latest_timestamp and event.timestamp > latest_timestamp
|
|
147
|
+
):
|
|
148
|
+
latest_state = state
|
|
149
|
+
latest_timestamp = event.timestamp
|
|
150
|
+
|
|
151
|
+
if not latest_state:
|
|
152
|
+
raise NoSuchTaskException(task_id)
|
|
153
|
+
return latest_state
|
|
154
|
+
|
|
155
|
+
def write_state(self, state: AsyncTaskState[R]) -> None:
|
|
156
|
+
event = Event(
|
|
157
|
+
type=RemoteStateManager._TASK_STATE_EVENT_TYPE.format(self.agent_name),
|
|
158
|
+
actor_id=self.actor_id,
|
|
159
|
+
timestamp=datetime.now(tz=timezone.utc),
|
|
160
|
+
message_id=self.message_id,
|
|
161
|
+
data=state.model_dump(),
|
|
162
|
+
)
|
|
163
|
+
self.interactions_service.save_event(event)
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: nora_lib
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.6.dev1
|
|
4
4
|
Summary: For making and coordinating agents and tools
|
|
5
5
|
Home-page: https://github.com/allenai/nora_lib
|
|
6
6
|
Requires-Python: >=3.9
|
|
7
7
|
Requires-Dist: pydantic<3,>=2
|
|
8
8
|
Requires-Dist: requests
|
|
9
|
+
Requires-Dist: boto3
|
|
9
10
|
Provides-Extra: dev
|
|
10
11
|
Requires-Dist: mypy; extra == "dev"
|
|
11
12
|
Requires-Dist: pytest; extra == "dev"
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import setuptools
|
|
2
2
|
|
|
3
|
-
runtime_requirements = ["pydantic>=2,<3", "requests"]
|
|
3
|
+
runtime_requirements = ["pydantic>=2,<3", "requests", "boto3"]
|
|
4
4
|
|
|
5
5
|
# For running tests, linting, etc
|
|
6
6
|
dev_requirements = ["mypy", "pytest", "black", "types-requests"]
|
|
7
7
|
|
|
8
8
|
setuptools.setup(
|
|
9
9
|
name="nora_lib",
|
|
10
|
-
version="0.0.
|
|
10
|
+
version="0.0.6.dev1",
|
|
11
11
|
description="For making and coordinating agents and tools",
|
|
12
12
|
url="https://github.com/allenai/nora_lib",
|
|
13
13
|
packages=setuptools.find_packages(exclude=(["tests"])),
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Handles IO for asynchronous task-related state.
|
|
3
|
-
Currently just reads/writes from local disk,
|
|
4
|
-
the best and most robust mechanism.
|
|
5
|
-
|
|
6
|
-
A StateManager instance must be initialized with
|
|
7
|
-
a concrete subclass of `AsyncTaskState`, as implemented
|
|
8
|
-
by dependent projects.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
import json
|
|
12
|
-
import os
|
|
13
|
-
from typing import Generic, Optional, Type
|
|
14
|
-
|
|
15
|
-
from nora_lib.tasks.models import AsyncTaskState, R, TASK_STATUSES
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class NoSuchTaskException(Exception):
|
|
19
|
-
def __init__(self, task_id: str):
|
|
20
|
-
self._task_id = task_id
|
|
21
|
-
|
|
22
|
-
def __str__(self):
|
|
23
|
-
return f"No record found for task {self._task_id}"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class StateManager(Generic[R]):
|
|
27
|
-
def __init__(self, task_state_class: Type[AsyncTaskState[R]], state_dir) -> None:
|
|
28
|
-
self._task_state_class = task_state_class
|
|
29
|
-
self._state_dir = state_dir
|
|
30
|
-
|
|
31
|
-
def read_state(self, task_id: str) -> AsyncTaskState[R]:
|
|
32
|
-
task_state_path = os.path.join(self._state_dir, f"{task_id}.json")
|
|
33
|
-
if not os.path.isfile(task_state_path):
|
|
34
|
-
raise NoSuchTaskException(task_id)
|
|
35
|
-
|
|
36
|
-
with open(task_state_path, "r") as f:
|
|
37
|
-
return self._task_state_class(**json.loads(f.read()))
|
|
38
|
-
|
|
39
|
-
def write_state(self, state: AsyncTaskState[R]) -> None:
|
|
40
|
-
task_state_path = os.path.join(self._state_dir, f"{state.task_id}.json")
|
|
41
|
-
with open(task_state_path, "w") as f:
|
|
42
|
-
json.dump(state.model_dump(), f)
|
|
43
|
-
|
|
44
|
-
def update_status(self, task_id: str, new_status: str) -> None:
|
|
45
|
-
state = self.read_state(task_id)
|
|
46
|
-
state.task_status = new_status
|
|
47
|
-
self.write_state(state)
|
|
48
|
-
|
|
49
|
-
def save_result(self, task_id: str, task_result: R) -> None:
|
|
50
|
-
state = self.read_state(task_id)
|
|
51
|
-
state.task_status = TASK_STATUSES["COMPLETED"]
|
|
52
|
-
state.task_result = task_result
|
|
53
|
-
self.write_state(state)
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|