nora-lib 0.0.1.dev0__tar.gz → 0.0.2__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.1.dev0 → nora_lib-0.0.2}/PKG-INFO +2 -1
- nora_lib-0.0.2/nora_lib/tasks/__init__.py +0 -0
- nora_lib-0.0.2/nora_lib/tasks/models.py +25 -0
- nora_lib-0.0.2/nora_lib/tasks/state.py +53 -0
- {nora_lib-0.0.1.dev0 → nora_lib-0.0.2}/nora_lib.egg-info/PKG-INFO +2 -1
- {nora_lib-0.0.1.dev0 → nora_lib-0.0.2}/nora_lib.egg-info/SOURCES.txt +7 -1
- {nora_lib-0.0.1.dev0 → nora_lib-0.0.2}/nora_lib.egg-info/requires.txt +1 -0
- {nora_lib-0.0.1.dev0 → nora_lib-0.0.2}/nora_lib.egg-info/top_level.txt +1 -0
- {nora_lib-0.0.1.dev0 → nora_lib-0.0.2}/setup.py +2 -2
- nora_lib-0.0.2/tests/tasks/__init__.py +0 -0
- nora_lib-0.0.2/tests/tasks/test_state.py +77 -0
- nora_lib-0.0.2/tests/test_placeholder.py +6 -0
- {nora_lib-0.0.1.dev0 → nora_lib-0.0.2}/README.md +0 -0
- {nora_lib-0.0.1.dev0 → nora_lib-0.0.2}/nora_lib/__init__.py +0 -0
- {nora_lib-0.0.1.dev0 → nora_lib-0.0.2}/nora_lib/py.typed +0 -0
- {nora_lib-0.0.1.dev0 → nora_lib-0.0.2}/nora_lib.egg-info/dependency_links.txt +0 -0
- {nora_lib-0.0.1.dev0 → nora_lib-0.0.2}/pyproject.toml +0 -0
- {nora_lib-0.0.1.dev0 → nora_lib-0.0.2}/setup.cfg +0 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: nora_lib
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.2
|
|
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
|
+
Requires-Dist: pydantic<3,>=2
|
|
7
8
|
Provides-Extra: dev
|
|
8
9
|
Requires-Dist: mypy; extra == "dev"
|
|
9
10
|
Requires-Dist: pytest; extra == "dev"
|
|
File without changes
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from typing import Any, Dict, Generic, Optional, TypeVar
|
|
2
|
+
from pydantic import BaseModel, Field
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
R = TypeVar("R", bound=BaseModel)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
TASK_STATUSES = dict(STARTED="STARTED", FAILED="FAILED", COMPLETED="COMPLETED")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AsyncTaskState(BaseModel, Generic[R]):
|
|
12
|
+
"""Models the current state of an asynchronous request."""
|
|
13
|
+
|
|
14
|
+
task_id: str = Field(
|
|
15
|
+
"Identifies the long-running task so that its status and eventual result"
|
|
16
|
+
"can be checked in follow-up calls."
|
|
17
|
+
)
|
|
18
|
+
estimated_time: str = Field(
|
|
19
|
+
description="How long we expect this task to take from start to finish."
|
|
20
|
+
)
|
|
21
|
+
task_status: str = Field(description="Current human-readable status of the task.")
|
|
22
|
+
task_result: Optional[R] = Field(description="Final result of the task.")
|
|
23
|
+
extra_state: Dict[str, Any] = Field(
|
|
24
|
+
description="Any extra task-specific state can go in here as free-form JSON-serializable dictionary."
|
|
25
|
+
)
|
|
@@ -0,0 +1,53 @@
|
|
|
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)
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: nora_lib
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.2
|
|
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
|
+
Requires-Dist: pydantic<3,>=2
|
|
7
8
|
Provides-Extra: dev
|
|
8
9
|
Requires-Dist: mypy; extra == "dev"
|
|
9
10
|
Requires-Dist: pytest; extra == "dev"
|
|
@@ -7,4 +7,10 @@ nora_lib.egg-info/PKG-INFO
|
|
|
7
7
|
nora_lib.egg-info/SOURCES.txt
|
|
8
8
|
nora_lib.egg-info/dependency_links.txt
|
|
9
9
|
nora_lib.egg-info/requires.txt
|
|
10
|
-
nora_lib.egg-info/top_level.txt
|
|
10
|
+
nora_lib.egg-info/top_level.txt
|
|
11
|
+
nora_lib/tasks/__init__.py
|
|
12
|
+
nora_lib/tasks/models.py
|
|
13
|
+
nora_lib/tasks/state.py
|
|
14
|
+
tests/test_placeholder.py
|
|
15
|
+
tests/tasks/__init__.py
|
|
16
|
+
tests/tasks/test_state.py
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import setuptools
|
|
2
2
|
|
|
3
|
-
runtime_requirements = []
|
|
3
|
+
runtime_requirements = ["pydantic>=2,<3"]
|
|
4
4
|
|
|
5
5
|
# For running tests, linting, etc
|
|
6
6
|
dev_requirements = ["mypy", "pytest", "black"]
|
|
7
7
|
|
|
8
8
|
setuptools.setup(
|
|
9
9
|
name="nora_lib",
|
|
10
|
-
version="0.0.
|
|
10
|
+
version="0.0.2",
|
|
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"])),
|
|
File without changes
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import tempfile
|
|
2
|
+
import unittest
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
from nora_lib.tasks.models import AsyncTaskState, TASK_STATUSES
|
|
7
|
+
from nora_lib.tasks.state import NoSuchTaskException, StateManager
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MyTaskResult(BaseModel):
|
|
11
|
+
a: str
|
|
12
|
+
b: int
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MyAsyncTaskState(AsyncTaskState[MyTaskResult]):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TestState(unittest.TestCase):
|
|
20
|
+
def test__can_read_and_write_state(self):
|
|
21
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
22
|
+
manager = StateManager(MyAsyncTaskState, tmpdir)
|
|
23
|
+
state = MyAsyncTaskState(
|
|
24
|
+
task_id="asdf",
|
|
25
|
+
estimated_time="40 days and 40 nights",
|
|
26
|
+
task_status="STARTED",
|
|
27
|
+
task_result=None,
|
|
28
|
+
extra_state={"foo": "bar"},
|
|
29
|
+
)
|
|
30
|
+
manager.write_state(state)
|
|
31
|
+
fetched_state = manager.read_state("asdf")
|
|
32
|
+
|
|
33
|
+
self.assertEqual(state, fetched_state)
|
|
34
|
+
|
|
35
|
+
def test__raises_an_error_if_referencing_nonexistent_task(self):
|
|
36
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
37
|
+
manager = StateManager(MyAsyncTaskState, tmpdir)
|
|
38
|
+
with self.assertRaises(NoSuchTaskException):
|
|
39
|
+
manager.read_state("asdf")
|
|
40
|
+
|
|
41
|
+
def test__allows_specific_update_of_status_field(self):
|
|
42
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
43
|
+
manager = StateManager(MyAsyncTaskState, tmpdir)
|
|
44
|
+
state = MyAsyncTaskState(
|
|
45
|
+
task_id="asdf",
|
|
46
|
+
estimated_time="40 days and 40 nights",
|
|
47
|
+
task_status="STARTED",
|
|
48
|
+
task_result=None,
|
|
49
|
+
extra_state={"foo": "bar"},
|
|
50
|
+
)
|
|
51
|
+
manager.write_state(state)
|
|
52
|
+
manager.update_status(state.task_id, "fail")
|
|
53
|
+
fetched_state = manager.read_state("asdf")
|
|
54
|
+
state.task_status = "fail"
|
|
55
|
+
|
|
56
|
+
self.assertEqual(state, fetched_state)
|
|
57
|
+
|
|
58
|
+
def test__allows_specific_update_of_result_field(self):
|
|
59
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
60
|
+
manager = StateManager(MyAsyncTaskState, tmpdir)
|
|
61
|
+
state = MyAsyncTaskState(
|
|
62
|
+
task_id="asdf",
|
|
63
|
+
estimated_time="40 days and 40 nights",
|
|
64
|
+
task_status="STARTED",
|
|
65
|
+
task_result=None,
|
|
66
|
+
extra_state={"foo": "bar"},
|
|
67
|
+
)
|
|
68
|
+
manager.write_state(state)
|
|
69
|
+
|
|
70
|
+
result = MyTaskResult(a="asdf", b=123)
|
|
71
|
+
manager.save_result(state.task_id, result)
|
|
72
|
+
fetched_state = manager.read_state("asdf")
|
|
73
|
+
|
|
74
|
+
state.task_status = TASK_STATUSES["COMPLETED"]
|
|
75
|
+
state.task_result = result
|
|
76
|
+
|
|
77
|
+
self.assertEqual(state, fetched_state)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|