spot-sdk-python 2.0.0b9__tar.gz → 2.0.0b10__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.
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/PKG-INFO +2 -2
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/README.md +1 -1
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/pyproject.toml +1 -1
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/__init__.py +2 -1
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/clients.py +16 -1
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/orchestrator.py +1 -1
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/retriever.py +16 -0
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/testing/fake_spot.py +19 -3
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/workflow.py +1 -5
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/_json.py +0 -0
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/app.py +0 -0
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/artifacts.py +0 -0
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/config.py +0 -0
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/email.py +0 -0
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/errors.py +0 -0
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/knowledge.py +0 -0
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/knowledge_tags.py +0 -0
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/llm.py +0 -0
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/logging.py +0 -0
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/manifest.py +0 -0
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/mime.py +0 -0
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/model_input.py +0 -0
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/py.typed +0 -0
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/settings.py +0 -0
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/signals.py +0 -0
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/testing/README.md +0 -0
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/testing/__init__.py +0 -0
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/testing/contract.py +0 -0
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/testing/fake_knowledge_client.py +0 -0
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/testing/fake_llm.py +0 -0
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/testing/settings.py +0 -0
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/testing/views.py +0 -0
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/urls.py +0 -0
- {spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/verdict.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spot-sdk-python
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.0b10
|
|
4
4
|
Summary: SPOT plugin SDK: the plugin contract, the plugin runtime, its clients and test helpers
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Author: SPOT Project
|
|
@@ -51,7 +51,7 @@ plugins share:
|
|
|
51
51
|
## Install
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
|
-
pip install spot-sdk-python==2.0.
|
|
54
|
+
pip install spot-sdk-python==2.0.0b10
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
Python 3.11 to 3.15. Version 2 implements plugin contract 2, for
|
|
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
4
4
|
|
|
5
5
|
[tool.poetry]
|
|
6
6
|
name = "spot-sdk-python"
|
|
7
|
-
version = "2.0.
|
|
7
|
+
version = "2.0.0b10"
|
|
8
8
|
description = "SPOT plugin SDK: the plugin contract, the plugin runtime, its clients and test helpers"
|
|
9
9
|
authors = ["SPOT Project <spot@sonn.lu>"]
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -25,7 +25,7 @@ from .orchestrator import (
|
|
|
25
25
|
RespondRequest,
|
|
26
26
|
StageOutcome,
|
|
27
27
|
)
|
|
28
|
-
from .retriever import IngestionRequest, IngestionResult, IngestionStatus
|
|
28
|
+
from .retriever import ActionReport, IngestionRequest, IngestionResult, IngestionStatus
|
|
29
29
|
from .settings import PluginSettings
|
|
30
30
|
from .verdict import AnalyzerVerdict, Score, Signal, VerdictClass
|
|
31
31
|
from .workflow import (
|
|
@@ -110,6 +110,7 @@ __all__ = [
|
|
|
110
110
|
"RespondRequest",
|
|
111
111
|
"StageOutcome",
|
|
112
112
|
# Ingestion
|
|
113
|
+
"ActionReport",
|
|
113
114
|
"IngestionRequest",
|
|
114
115
|
"IngestionResult",
|
|
115
116
|
"IngestionStatus",
|
|
@@ -27,7 +27,12 @@ from pydantic import BaseModel, SecretStr, ValidationError
|
|
|
27
27
|
|
|
28
28
|
from .email import Envelope
|
|
29
29
|
from .orchestrator import OrchestrationResult
|
|
30
|
-
from .retriever import
|
|
30
|
+
from .retriever import (
|
|
31
|
+
ActionReport,
|
|
32
|
+
IngestionPriority,
|
|
33
|
+
IngestionRequest,
|
|
34
|
+
IngestionResult,
|
|
35
|
+
)
|
|
31
36
|
from .settings import PluginSettings
|
|
32
37
|
|
|
33
38
|
# The bytes of a raw message encoded at a time: a few milliseconds of a thread
|
|
@@ -219,6 +224,16 @@ class SpotClient:
|
|
|
219
224
|
)
|
|
220
225
|
return read_answer(IngestionResult, response, "POST", "/internal/ingest")
|
|
221
226
|
|
|
227
|
+
async def report_action(self, job_id: str, action: str, *, applied: bool) -> None:
|
|
228
|
+
"""Tell SPOT what the retriever did with a message: ``action``, and whether
|
|
229
|
+
it was ``applied`` (in observe, it is not) (scope ``ingest``)."""
|
|
230
|
+
report = ActionReport(action=action, applied=applied)
|
|
231
|
+
await self._call(
|
|
232
|
+
"POST",
|
|
233
|
+
f"/internal/jobs/{quote(job_id, safe='')}/action",
|
|
234
|
+
content=json_body(report.model_dump(mode="json")),
|
|
235
|
+
)
|
|
236
|
+
|
|
222
237
|
async def raw(self, email_id: UUID) -> bytes:
|
|
223
238
|
"""The stored message, as received (scope ``content:read``)."""
|
|
224
239
|
response = await self._call("GET", f"/internal/emails/{email_id}/raw")
|
|
@@ -88,5 +88,5 @@ class RespondRequest(BaseModel):
|
|
|
88
88
|
decided_by: str
|
|
89
89
|
signals: list[AttributedSignal]
|
|
90
90
|
mode: ResponseMode = Field(
|
|
91
|
-
description="The
|
|
91
|
+
description="The job's mode (its connection's and its route's); a responder acts only in enforce"
|
|
92
92
|
)
|
|
@@ -35,6 +35,7 @@ from pydantic import (
|
|
|
35
35
|
|
|
36
36
|
from .email import Envelope
|
|
37
37
|
from .verdict import Score, VerdictClass
|
|
38
|
+
from .workflow import ResponseMode
|
|
38
39
|
|
|
39
40
|
|
|
40
41
|
def _decode_message(value: object) -> bytes:
|
|
@@ -115,3 +116,18 @@ class IngestionResult(BaseModel):
|
|
|
115
116
|
error: str | None = Field(
|
|
116
117
|
default=None, description="Why there is no verdict, when the status is timeout"
|
|
117
118
|
)
|
|
119
|
+
mode: ResponseMode = Field(
|
|
120
|
+
default="observe",
|
|
121
|
+
description=(
|
|
122
|
+
"The job's mode: enforce only when its connection and its route both "
|
|
123
|
+
"are; a retriever applies its actions only in enforce"
|
|
124
|
+
),
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class ActionReport(BaseModel):
|
|
129
|
+
"""The body of ``POST /internal/jobs/{job_id}/action``: what a retriever
|
|
130
|
+
did with a message (or, in observe, would have done)."""
|
|
131
|
+
|
|
132
|
+
action: str = Field(pattern=r"^[a-z][a-z0-9_]{0,31}$")
|
|
133
|
+
applied: bool
|
|
@@ -21,24 +21,33 @@ from ..email import Envelope
|
|
|
21
21
|
from ..mime import PartNotFoundError, part_bytes
|
|
22
22
|
from ..orchestrator import OrchestrationResult
|
|
23
23
|
from ..retriever import (
|
|
24
|
+
ActionReport,
|
|
24
25
|
IngestionPriority,
|
|
25
26
|
IngestionRequest,
|
|
26
27
|
IngestionResult,
|
|
27
28
|
IngestionStatus,
|
|
28
29
|
)
|
|
29
30
|
from ..verdict import VerdictClass
|
|
31
|
+
from ..workflow import ResponseMode
|
|
30
32
|
|
|
31
33
|
|
|
32
34
|
class FakeSpot:
|
|
33
35
|
"""In-memory gateway with the methods of ``SpotClient``."""
|
|
34
36
|
|
|
35
37
|
def __init__(
|
|
36
|
-
self,
|
|
38
|
+
self,
|
|
39
|
+
*,
|
|
40
|
+
verdict: VerdictClass = VerdictClass.BENIGN,
|
|
41
|
+
score: int = 0,
|
|
42
|
+
mode: ResponseMode = "observe",
|
|
37
43
|
) -> None:
|
|
38
|
-
"""``verdict`` and ``score`` answer an ingest that waits for the verdict
|
|
44
|
+
"""``verdict`` and ``score`` answer an ingest that waits for the verdict;
|
|
45
|
+
``mode`` is the mode of every job."""
|
|
39
46
|
self.verdict = verdict
|
|
40
47
|
self.score = score
|
|
48
|
+
self.mode = mode
|
|
41
49
|
self.ingested: list[IngestionRequest] = []
|
|
50
|
+
self.actions: list[tuple[str, ActionReport]] = []
|
|
42
51
|
self._emails: dict[UUID, bytes] = {}
|
|
43
52
|
self._results: dict[str, OrchestrationResult] = {}
|
|
44
53
|
|
|
@@ -73,7 +82,10 @@ class FakeSpot:
|
|
|
73
82
|
email_id = self.add_email(request.raw)
|
|
74
83
|
if not wait_for_verdict:
|
|
75
84
|
return IngestionResult(
|
|
76
|
-
job_id=job_id,
|
|
85
|
+
job_id=job_id,
|
|
86
|
+
email_id=email_id,
|
|
87
|
+
status=IngestionStatus.ACCEPTED,
|
|
88
|
+
mode=self.mode,
|
|
77
89
|
)
|
|
78
90
|
return IngestionResult(
|
|
79
91
|
job_id=job_id,
|
|
@@ -81,8 +93,12 @@ class FakeSpot:
|
|
|
81
93
|
status=IngestionStatus.ANALYZED,
|
|
82
94
|
verdict=self.verdict,
|
|
83
95
|
score=self.score,
|
|
96
|
+
mode=self.mode,
|
|
84
97
|
)
|
|
85
98
|
|
|
99
|
+
async def report_action(self, job_id: str, action: str, *, applied: bool) -> None:
|
|
100
|
+
self.actions.append((job_id, ActionReport(action=action, applied=applied)))
|
|
101
|
+
|
|
86
102
|
async def raw(self, email_id: UUID) -> bytes:
|
|
87
103
|
return self._stored(email_id, f"/internal/emails/{email_id}/raw")
|
|
88
104
|
|
|
@@ -10,7 +10,7 @@ from pydantic import BaseModel, Field, model_validator
|
|
|
10
10
|
from .verdict import VerdictClass
|
|
11
11
|
|
|
12
12
|
ResponseMode = Literal["observe", "enforce"]
|
|
13
|
-
"""Whether responders act on verdicts (``enforce``) or only record them (``observe``)."""
|
|
13
|
+
"""Whether responders and retrievers act on verdicts (``enforce``) or only record them (``observe``): the mode of a job, from its connection and its route."""
|
|
14
14
|
|
|
15
15
|
DECISIVE_SIGNAL_PATTERN = r"^[a-z0-9_]+(\.[a-z0-9_]+)*\.([a-z0-9_]+|\*)$"
|
|
16
16
|
"""A signal name, or a prefix ending in ``.*``."""
|
|
@@ -181,10 +181,6 @@ class Workflow(BaseModel):
|
|
|
181
181
|
on_verdict: dict[VerdictClass, list[str]] = Field(
|
|
182
182
|
default_factory=dict, description="Responder ids to call, by verdict"
|
|
183
183
|
)
|
|
184
|
-
response_mode: ResponseMode = Field(
|
|
185
|
-
default="observe",
|
|
186
|
-
description="Sent to responders as mode; a responder acts only in enforce",
|
|
187
|
-
)
|
|
188
184
|
|
|
189
185
|
@model_validator(mode="after")
|
|
190
186
|
def _decision_stages_exist(self) -> Self:
|
|
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
|
|
File without changes
|
|
File without changes
|
{spot_sdk_python-2.0.0b9 → spot_sdk_python-2.0.0b10}/spot_sdk/testing/fake_knowledge_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|