spot-sdk-python 2.1.1__tar.gz → 2.2.0b1__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.1.1 → spot_sdk_python-2.2.0b1}/PKG-INFO +1 -1
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/pyproject.toml +1 -1
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/pyproject.toml.orig +1 -1
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/__init__.py +3 -2
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/clients.py +31 -1
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/mime.py +12 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/testing/README.md +1 -1
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/testing/fake_spot.py +1 -1
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/README.md +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/_json.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/app.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/artifacts.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/config.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/correlation.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/email.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/errors.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/hosting.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/knowledge.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/knowledge_tags.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/llm.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/logging.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/manifest.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/model_input.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/orchestrator.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/proxy.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/py.typed +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/retriever.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/settings.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/signals.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/testing/__init__.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/testing/container.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/testing/contract.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/testing/corpus.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/testing/engine.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/testing/fake_knowledge_client.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/testing/fake_llm.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/testing/metrics.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/testing/report.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/testing/settings.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/testing/views.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/urls.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/verdict.py +0 -0
- {spot_sdk_python-2.1.1 → spot_sdk_python-2.2.0b1}/spot_sdk/workflow.py +0 -0
|
@@ -44,7 +44,7 @@ from .workflow import (
|
|
|
44
44
|
|
|
45
45
|
if TYPE_CHECKING:
|
|
46
46
|
from .app import CorrelationMiddleware, PluginHealth, SyncState, create_plugin_app
|
|
47
|
-
from .clients import SpotAPIError, SpotClient
|
|
47
|
+
from .clients import SpotAPIError, SpotClient, SpotGateway
|
|
48
48
|
from .knowledge import KnowledgeClient, KnowledgeDocument, chunk_text, content_hash
|
|
49
49
|
from .llm import LLMClient, LLMOutputError, LLMToolCall, LLMToolCalls, LLMToolRound
|
|
50
50
|
|
|
@@ -55,7 +55,7 @@ _LAZY = {
|
|
|
55
55
|
("CorrelationMiddleware", "PluginHealth", "SyncState", "create_plugin_app"),
|
|
56
56
|
".app",
|
|
57
57
|
),
|
|
58
|
-
**dict.fromkeys(("SpotAPIError", "SpotClient"), ".clients"),
|
|
58
|
+
**dict.fromkeys(("SpotAPIError", "SpotClient", "SpotGateway"), ".clients"),
|
|
59
59
|
**dict.fromkeys(
|
|
60
60
|
("KnowledgeClient", "KnowledgeDocument", "chunk_text", "content_hash"),
|
|
61
61
|
".knowledge",
|
|
@@ -142,6 +142,7 @@ __all__ = [
|
|
|
142
142
|
"LLMToolRound",
|
|
143
143
|
"SpotAPIError",
|
|
144
144
|
"SpotClient",
|
|
145
|
+
"SpotGateway",
|
|
145
146
|
# Knowledge Store
|
|
146
147
|
"KnowledgeClient",
|
|
147
148
|
"KnowledgeDocument",
|
|
@@ -10,6 +10,7 @@ the route and the status, never the content.
|
|
|
10
10
|
- Analyzers fetch the raw message or one decoded part with
|
|
11
11
|
:meth:`SpotClient.raw` and :meth:`SpotClient.part`.
|
|
12
12
|
- Responders read a workflow's result with :meth:`SpotClient.result`.
|
|
13
|
+
- :class:`SpotGateway` types a plugin's client parameter, so a test passes a fake.
|
|
13
14
|
"""
|
|
14
15
|
|
|
15
16
|
from __future__ import annotations
|
|
@@ -17,7 +18,7 @@ from __future__ import annotations
|
|
|
17
18
|
import asyncio
|
|
18
19
|
import base64
|
|
19
20
|
import json
|
|
20
|
-
from typing import Any, TypeVar
|
|
21
|
+
from typing import Any, Protocol, TypeVar
|
|
21
22
|
from urllib.parse import quote
|
|
22
23
|
from uuid import UUID
|
|
23
24
|
|
|
@@ -155,6 +156,35 @@ def ingestion_body(
|
|
|
155
156
|
return b"".join([b'{"raw": "', *pieces, b'", ', others[1:]])
|
|
156
157
|
|
|
157
158
|
|
|
159
|
+
class SpotGateway(Protocol):
|
|
160
|
+
"""The gateway's plugin routes, as a plugin calls them.
|
|
161
|
+
|
|
162
|
+
A plugin types its parameter with it. ``SpotClient`` calls the gateway over
|
|
163
|
+
HTTP, and ``spot_sdk.testing.FakeSpot`` holds it in memory, so a test passes
|
|
164
|
+
a ``FakeSpot`` with no cast.
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
async def ingest(
|
|
168
|
+
self,
|
|
169
|
+
raw: bytes,
|
|
170
|
+
*,
|
|
171
|
+
envelope: Envelope | None = None,
|
|
172
|
+
priority: IngestionPriority = "normal",
|
|
173
|
+
wait_for_verdict: bool = False,
|
|
174
|
+
timeout_ms: int = 30_000,
|
|
175
|
+
) -> IngestionResult: ...
|
|
176
|
+
|
|
177
|
+
async def report_action(
|
|
178
|
+
self, job_id: str, action: str, *, applied: bool
|
|
179
|
+
) -> None: ...
|
|
180
|
+
|
|
181
|
+
async def raw(self, email_id: UUID) -> bytes: ...
|
|
182
|
+
|
|
183
|
+
async def part(self, email_id: UUID, part_id: str) -> tuple[bytes, str]: ...
|
|
184
|
+
|
|
185
|
+
async def result(self, job_id: str) -> OrchestrationResult: ...
|
|
186
|
+
|
|
187
|
+
|
|
158
188
|
class SpotClient:
|
|
159
189
|
"""The api-gateway's plugin routes, called with the plugin's own token."""
|
|
160
190
|
|
|
@@ -1772,6 +1772,18 @@ def _addresses(value: str, cut: _Cutter, punycode: _Punycode) -> Iterator[Addres
|
|
|
1772
1772
|
)
|
|
1773
1773
|
|
|
1774
1774
|
|
|
1775
|
+
def mailbox(value: str) -> Address | None:
|
|
1776
|
+
"""The first mailbox of an address header's ``value``, as :func:`parse` reads
|
|
1777
|
+
a From into ``EmailView.from_``.
|
|
1778
|
+
|
|
1779
|
+
``value`` is a header's value as the view keeps it (unfolded, RFC 2047
|
|
1780
|
+
decoded). A value that does not parse is kept whole, with no domain; an
|
|
1781
|
+
empty one is None. Its strings are cut to the view's budgets, its A-labels
|
|
1782
|
+
decoded within one message's budget, as :func:`parse` does.
|
|
1783
|
+
"""
|
|
1784
|
+
return next(_addresses(value, _Cutter(), _Punycode()), None)
|
|
1785
|
+
|
|
1786
|
+
|
|
1775
1787
|
def _date(value: str) -> datetime | None:
|
|
1776
1788
|
try:
|
|
1777
1789
|
return parsedate_to_datetime(value)
|
|
@@ -11,7 +11,7 @@ shows them in use.
|
|
|
11
11
|
| `assert_contract(app, eml, *, token, timeout=10.0)` | waits until the plugin has started (`/health` is `healthy`), runs the app's `/internal/analyze` on a parsed `.eml` as core does, and checks the token and the `AnalyzerVerdict` |
|
|
12
12
|
| `assert_bounds_agree(manifest, settings_cls)` | checks that the manifest states the bounds (`ge`, `le`, `pattern`...) of the settings model |
|
|
13
13
|
| `assert_provider_contract(app, knowledge, *, token)` | runs two syncs of a context provider as core does, and checks the token, `/internal/sync`, `/internal/state` and the documents written to `knowledge` |
|
|
14
|
-
| `FakeSpot` | `SpotClient` in memory: `ingest`, `raw`, `part`, `result`; `app(token=, knowledge=)` serves it over HTTP |
|
|
14
|
+
| `FakeSpot` | `SpotClient` in memory: `ingest`, `report_action`, `raw`, `part`, `result`; `app(token=, knowledge=)` serves it over HTTP |
|
|
15
15
|
| `FakeKnowledgeClient` | `KnowledgeClient` in memory, with naive word matching |
|
|
16
16
|
| `FakeLLM` | `LLMClient` with scripted answers, recording each request |
|
|
17
17
|
|
|
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
|
|
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
|