spot-sdk-python 2.1.2__py3-none-any.whl → 2.2.0b2__py3-none-any.whl

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/__init__.py CHANGED
@@ -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",
spot_sdk/clients.py CHANGED
@@ -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
 
spot_sdk/mime.py CHANGED
@@ -1776,6 +1776,18 @@ def _addresses(value: str, cut: _Cutter, punycode: _Punycode) -> Iterator[Addres
1776
1776
  )
1777
1777
 
1778
1778
 
1779
+ def mailbox(value: str) -> Address | None:
1780
+ """The first mailbox of an address header's ``value``, as :func:`parse` reads
1781
+ a From into ``EmailView.from_``.
1782
+
1783
+ ``value`` is a header's value as the view keeps it (unfolded, RFC 2047
1784
+ decoded). A value that does not parse is kept whole, with no domain; an
1785
+ empty one is None. Its strings are cut to the view's budgets, its A-labels
1786
+ decoded within one message's budget, as :func:`parse` does.
1787
+ """
1788
+ return next(_addresses(value, _Cutter(), _Punycode()), None)
1789
+
1790
+
1779
1791
  def _date(value: str) -> datetime | None:
1780
1792
  try:
1781
1793
  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
 
@@ -58,7 +58,7 @@ class _Query(BaseModel):
58
58
 
59
59
 
60
60
  class FakeSpot:
61
- """In-memory gateway with the methods of ``SpotClient``."""
61
+ """In-memory gateway with the methods of ``SpotClient``: a ``SpotGateway``."""
62
62
 
63
63
  def __init__(
64
64
  self,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spot-sdk-python
3
- Version: 2.1.2
3
+ Version: 2.2.0b2
4
4
  Summary: SPOT plugin SDK: the plugin contract, the plugin runtime, its clients and test helpers
5
5
  Author: SPOT Project
6
6
  Author-email: SPOT Project <spot@sonn.lu>
@@ -1,8 +1,8 @@
1
- spot_sdk/__init__.py,sha256=l-o_lRVVvHz6eLt3gtUbSEghPCJg-L8XNFxTQXeRokk,4077
1
+ spot_sdk/__init__.py,sha256=8ncEYjaoAewQBh20V9HtZDzvhv4jfYbCpedu8ihk21g,4124
2
2
  spot_sdk/_json.py,sha256=qK1b4EEgc6pHQn-M3_WnMN988N6sw4kvOUr7V-iEk74,1766
3
3
  spot_sdk/app.py,sha256=xVEZatG4KPnJZT5MBT_pjJxa389X-eljjFSBNkW1Nzo,25979
4
4
  spot_sdk/artifacts.py,sha256=mcWKIv1A-v3-bNQPxDVopJLGOfPk6YdiskIRmyV024U,7883
5
- spot_sdk/clients.py,sha256=sf6Mbc9Hw8pcAiD0XuTdM3Yg6b-oQJ6Uyq99Qsdw3-Y,9703
5
+ spot_sdk/clients.py,sha256=GYjVP3duiMo-jLv215JLmMyfFLJL0EavfhHCek231FU,10664
6
6
  spot_sdk/config.py,sha256=sMSCOI_eVlcNiPamj4Nyuq7p44KZr06LFZVDlYgcqiI,1560
7
7
  spot_sdk/correlation.py,sha256=tgIL122V_d0htaR005zBHt2iLOoQnj3E-X-yKyPT6dc,2184
8
8
  spot_sdk/email.py,sha256=ifZHG9M0x-sTrOtohrU1NStsL4qeVddn18JoqLFawIQ,7313
@@ -13,7 +13,7 @@ spot_sdk/knowledge_tags.py,sha256=BGhDsNeF8Q0MMNEIZjWrgQWjwH_Vf7dGBNN_hlBqTXs,97
13
13
  spot_sdk/llm.py,sha256=IEWhBbEat-7oHSNDg9_QOb7ylzv2g0B9uACpZq9-vMk,18610
14
14
  spot_sdk/logging.py,sha256=Tn_InLtrmy0NBFbkVXreVeXBazaz2bSdqEL1gxQa_Oo,10772
15
15
  spot_sdk/manifest.py,sha256=P38sJ0lPzAeAcd1hY2Qa9QSPrlLSVLpE6mTt-3l7j1Q,12245
16
- spot_sdk/mime.py,sha256=v6E3hgTXp0_MtYiZcCUmjc9HHl3lH_bezRNCnza0EVE,121283
16
+ spot_sdk/mime.py,sha256=iO6Lu2NGtESFn7h_9KWj0Q6Aki6nCIzXNGzLxaksJ4o,121815
17
17
  spot_sdk/model_input.py,sha256=9sWrix9rH2h1oe49GWDOTy9zwsxWLSXtBfyhcYXsZh4,7469
18
18
  spot_sdk/orchestrator.py,sha256=r_-SWCrDLEjBcb_zn7KdMXttzbFvf1LhLx4FW8kq3q4,2665
19
19
  spot_sdk/proxy.py,sha256=HJUPnzxw3e2aZQXMBZmQEGDRPEnFI3Kyh0zaSPJcE5g,3529
@@ -21,7 +21,7 @@ spot_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
21
  spot_sdk/retriever.py,sha256=fQh09-KusdO3EMhIeHX_H2G6N_K57XUk-AKZ7fLTAGk,4374
22
22
  spot_sdk/settings.py,sha256=4FN8WYSEoxY-Gf4D0jaSU-5z6o0EqrQXFbnD03qhd0Q,2884
23
23
  spot_sdk/signals.py,sha256=DkTqCSCcGbbtJjoTFrXExY9TcKFDXb-4SLR8wAgJUx0,1517
24
- spot_sdk/testing/README.md,sha256=N-42IBZOCxf0KbrfZ0nO64eBEC-l2hlQAjq4qIRJLQU,2786
24
+ spot_sdk/testing/README.md,sha256=PBLlLySANJIXGGcSoSXQtUtA2kjfSkSx8w4F4o60ia8,2803
25
25
  spot_sdk/testing/__init__.py,sha256=4pTiWDdyYBFJwQnrhiC8PK8Ag-puKGEAHQG4hv8zfRs,555
26
26
  spot_sdk/testing/container.py,sha256=I4R-K-sWjT-AHPf-DoXacrBvLO-2fVqxPLwTgv0XofM,18504
27
27
  spot_sdk/testing/contract.py,sha256=t4J9HYIw2GF9esViY9362HYWoQunI3t9q6SraQhl0gA,10720
@@ -29,7 +29,7 @@ spot_sdk/testing/corpus.py,sha256=O1tEjwOR2VHPI_kWelQmvFjUXs_0OUtUEFtKfpdighc,92
29
29
  spot_sdk/testing/engine.py,sha256=IviyvQGvSGUiUSaK4Bbpv5O8sRs0LCpw49pXhR3kCZI,16280
30
30
  spot_sdk/testing/fake_knowledge_client.py,sha256=akq8N0xkI5HUlH8XHBq4ZKdEiNBiLwT4OrVaZZRadgs,6395
31
31
  spot_sdk/testing/fake_llm.py,sha256=8CZV1ZiEXOd6AK1oSk_9R8nvzzf5w1EEfPOYu3f3ruo,2538
32
- spot_sdk/testing/fake_spot.py,sha256=ep1BvYybhQKwO5uLe0KC0TwM5685Jv7HXANm2RLB1tM,8008
32
+ spot_sdk/testing/fake_spot.py,sha256=ReZcw-rJU_-bJw3nayjaiUbSsSkLbvTtbah9b6vPgG4,8027
33
33
  spot_sdk/testing/metrics.py,sha256=sXdTw7WPCgyobmjZFk1WlX_qL7oaOGXfQ67zqd8QFY4,5062
34
34
  spot_sdk/testing/report.py,sha256=eToJgYPt0uYMyso87kskDZngKIEHU00RVGYDgsR4hFw,4248
35
35
  spot_sdk/testing/settings.py,sha256=lRwLD41qIV4ejmJJQbIPAJO2x7u8ieRpl1aTYWg9j0U,5113
@@ -37,6 +37,6 @@ spot_sdk/testing/views.py,sha256=Su45wOqCQG_O2WcpZhFnCZCLEc8yd9KsUl3YkjvvM9g,164
37
37
  spot_sdk/urls.py,sha256=pAz5Zara9UkNRoN2Vtx-33pARfBDy3JIA8xi-bkFdmk,1637
38
38
  spot_sdk/verdict.py,sha256=FUtAcsajJKgO_LaAfEJtHYq1K9BwG7a6sfpVkITjs_w,4429
39
39
  spot_sdk/workflow.py,sha256=sxD_Ms-t8Emv2vn4vf7ftJRIsPR9Fy7LumwFji60LP0,6993
40
- spot_sdk_python-2.1.2.dist-info/WHEEL,sha256=rp-k_VZaX8oABFEtLwG_Df95p8LSKt9-uPyQ4jKH0P4,81
41
- spot_sdk_python-2.1.2.dist-info/METADATA,sha256=1ocSieJmnb1bh1TsPDoK_VYlm250Kq3fGe-yTzh08gQ,4076
42
- spot_sdk_python-2.1.2.dist-info/RECORD,,
40
+ spot_sdk_python-2.2.0b2.dist-info/WHEEL,sha256=rp-k_VZaX8oABFEtLwG_Df95p8LSKt9-uPyQ4jKH0P4,81
41
+ spot_sdk_python-2.2.0b2.dist-info/METADATA,sha256=qnx5AQ4N68iJ6eJYaB2Qtx_TurAQ24u9k32LSIzLqH8,4078
42
+ spot_sdk_python-2.2.0b2.dist-info/RECORD,,