e2a 2.1.0__tar.gz → 2.2.0__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.
- {e2a-2.1.0 → e2a-2.2.0}/PKG-INFO +2 -2
- {e2a-2.1.0 → e2a-2.2.0}/README.md +1 -1
- {e2a-2.1.0 → e2a-2.2.0}/pyproject.toml +1 -1
- {e2a-2.1.0 → e2a-2.2.0}/src/e2a/v1/async_client.py +24 -3
- {e2a-2.1.0 → e2a-2.2.0}/src/e2a/v1/client.py +31 -5
- {e2a-2.1.0 → e2a-2.2.0}/src/e2a/v1/handler.py +8 -5
- {e2a-2.1.0 → e2a-2.2.0}/tests/test_v1_async_client.py +30 -0
- {e2a-2.1.0 → e2a-2.2.0}/tests/test_v1_client.py +34 -0
- {e2a-2.1.0 → e2a-2.2.0}/.gitignore +0 -0
- {e2a-2.1.0 → e2a-2.2.0}/codegen-requirements.txt +0 -0
- {e2a-2.1.0 → e2a-2.2.0}/src/e2a/__init__.py +0 -0
- {e2a-2.1.0 → e2a-2.2.0}/src/e2a/v1/__init__.py +0 -0
- {e2a-2.1.0 → e2a-2.2.0}/src/e2a/v1/api.py +0 -0
- {e2a-2.1.0 → e2a-2.2.0}/src/e2a/v1/generated/__init__.py +0 -0
- {e2a-2.1.0 → e2a-2.2.0}/src/e2a/v1/generated/_internal.py +0 -0
- {e2a-2.1.0 → e2a-2.2.0}/src/e2a/v1/generated/github_com_Mnexa_AI_e2a_internal_identity.py +0 -0
- {e2a-2.1.0 → e2a-2.2.0}/src/e2a/v1/generated/internal_agent.py +0 -0
- {e2a-2.1.0 → e2a-2.2.0}/src/e2a/v1/websocket.py +0 -0
- {e2a-2.1.0 → e2a-2.2.0}/tests/__init__.py +0 -0
- {e2a-2.1.0 → e2a-2.2.0}/tests/test_contract.py +0 -0
- {e2a-2.1.0 → e2a-2.2.0}/tests/test_e2e.py +0 -0
- {e2a-2.1.0 → e2a-2.2.0}/tests/test_exports.py +0 -0
- {e2a-2.1.0 → e2a-2.2.0}/tests/test_generated_models.py +0 -0
- {e2a-2.1.0 → e2a-2.2.0}/tests/test_v1_api.py +0 -0
- {e2a-2.1.0 → e2a-2.2.0}/tests/test_v1_handler.py +0 -0
- {e2a-2.1.0 → e2a-2.2.0}/tests/test_v1_websocket.py +0 -0
- {e2a-2.1.0 → e2a-2.2.0}/uv.lock +0 -0
{e2a-2.1.0 → e2a-2.2.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: e2a
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Python SDK for the e2a protocol — email-to-agent authentication
|
|
5
5
|
Project-URL: Homepage, https://e2a.dev
|
|
6
6
|
Project-URL: Repository, https://github.com/Mnexa-AI/e2a
|
|
@@ -431,7 +431,7 @@ All claim fields (`message_id`, `sender`, `recipient`, `to`, `cc`, `subject`, `t
|
|
|
431
431
|
High-level sync client. `api_key` falls back to `E2A_API_KEY` env var.
|
|
432
432
|
|
|
433
433
|
- `client.parse_webhook(body, secret=None)` → `InboundEmail` — parse + HMAC-verify (recommended for webhook handlers). Reads `E2A_WEBHOOK_SECRET` if no secret is passed; raises `PermissionError` on bad signature.
|
|
434
|
-
- `client.parse(body)` → `InboundEmail` —
|
|
434
|
+
- `client.parse(body)` → `InboundEmail` — *deprecated since 2.2, removed in 3.0.* Accepts bytes, str, dict, or `MessageDetail` and returns an unverified email. Use `parse_webhook` for webhook handlers, or `email.unverified_payload` for inspection without verification. Calling `parse` emits a `DeprecationWarning`.
|
|
435
435
|
- `client.get_message(message_id)` → `InboundEmail` — pre-verified (REST channel auth)
|
|
436
436
|
- `client.get_messages(status="unread", page_size=50)` → `MessageList`
|
|
437
437
|
- `client.reply(message_id, body, ...)` → `SendResult`
|
|
@@ -397,7 +397,7 @@ All claim fields (`message_id`, `sender`, `recipient`, `to`, `cc`, `subject`, `t
|
|
|
397
397
|
High-level sync client. `api_key` falls back to `E2A_API_KEY` env var.
|
|
398
398
|
|
|
399
399
|
- `client.parse_webhook(body, secret=None)` → `InboundEmail` — parse + HMAC-verify (recommended for webhook handlers). Reads `E2A_WEBHOOK_SECRET` if no secret is passed; raises `PermissionError` on bad signature.
|
|
400
|
-
- `client.parse(body)` → `InboundEmail` —
|
|
400
|
+
- `client.parse(body)` → `InboundEmail` — *deprecated since 2.2, removed in 3.0.* Accepts bytes, str, dict, or `MessageDetail` and returns an unverified email. Use `parse_webhook` for webhook handlers, or `email.unverified_payload` for inspection without verification. Calling `parse` emits a `DeprecationWarning`.
|
|
401
401
|
- `client.get_message(message_id)` → `InboundEmail` — pre-verified (REST channel auth)
|
|
402
402
|
- `client.get_messages(status="unread", page_size=50)` → `MessageList`
|
|
403
403
|
- `client.reply(message_id, body, ...)` → `SendResult`
|
|
@@ -10,6 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
import base64
|
|
11
11
|
import json
|
|
12
12
|
import os
|
|
13
|
+
import warnings
|
|
13
14
|
from typing import TYPE_CHECKING, Any, AsyncIterator, Optional
|
|
14
15
|
from urllib.parse import quote
|
|
15
16
|
|
|
@@ -306,13 +307,33 @@ class AsyncE2AClient:
|
|
|
306
307
|
) -> AsyncInboundEmail:
|
|
307
308
|
"""Parse a webhook payload into an AsyncInboundEmail.
|
|
308
309
|
|
|
310
|
+
.. deprecated:: 2.2
|
|
311
|
+
Use :meth:`parse_webhook` for webhook handlers (parse + verify
|
|
312
|
+
in one call) or :attr:`AsyncInboundEmail.unverified_payload`
|
|
313
|
+
for inspection without verification. ``parse`` will be removed
|
|
314
|
+
in 3.0.
|
|
315
|
+
|
|
309
316
|
Synchronous (no I/O). The returned email's ``.reply()`` is async.
|
|
310
317
|
|
|
311
318
|
Returns an *unverified* AsyncInboundEmail — claim fields raise
|
|
312
319
|
:class:`UnverifiedEmailError` until you call
|
|
313
|
-
:meth:`AsyncInboundEmail.verify_signature`.
|
|
314
|
-
prefer :meth:`parse_webhook` which combines parse + verify.
|
|
320
|
+
:meth:`AsyncInboundEmail.verify_signature`.
|
|
315
321
|
"""
|
|
322
|
+
warnings.warn(
|
|
323
|
+
"AsyncE2AClient.parse() is deprecated and will be removed in 3.0. "
|
|
324
|
+
"For webhook handlers, use client.parse_webhook(body) — it "
|
|
325
|
+
"parses and HMAC-verifies in one call. For inspection without "
|
|
326
|
+
"verification, use email.unverified_payload after parse_webhook.",
|
|
327
|
+
DeprecationWarning,
|
|
328
|
+
stacklevel=2,
|
|
329
|
+
)
|
|
330
|
+
return self._parse_unverified(body)
|
|
331
|
+
|
|
332
|
+
def _parse_unverified(
|
|
333
|
+
self,
|
|
334
|
+
body: bytes | str | dict[str, Any] | MessageDetail,
|
|
335
|
+
) -> AsyncInboundEmail:
|
|
336
|
+
"""Internal parse without the deprecation warning."""
|
|
316
337
|
if isinstance(body, MessageDetail):
|
|
317
338
|
data = body.model_dump(by_alias=True)
|
|
318
339
|
elif isinstance(body, dict):
|
|
@@ -334,7 +355,7 @@ class AsyncE2AClient:
|
|
|
334
355
|
See :meth:`E2AClient.parse_webhook` — identical contract.
|
|
335
356
|
Synchronous despite living on the async client (no I/O).
|
|
336
357
|
"""
|
|
337
|
-
email = self.
|
|
358
|
+
email = self._parse_unverified(body)
|
|
338
359
|
if not email.verify_signature(secret):
|
|
339
360
|
raise PermissionError("HMAC signature verification failed")
|
|
340
361
|
return email
|
|
@@ -9,6 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
import base64
|
|
10
10
|
import json
|
|
11
11
|
import os
|
|
12
|
+
import warnings
|
|
12
13
|
from typing import Any, Optional
|
|
13
14
|
|
|
14
15
|
from e2a.v1.api import E2AApi
|
|
@@ -92,14 +93,39 @@ class E2AClient:
|
|
|
92
93
|
) -> InboundEmail:
|
|
93
94
|
"""Parse a webhook payload or MessageDetail into an InboundEmail.
|
|
94
95
|
|
|
96
|
+
.. deprecated:: 2.2
|
|
97
|
+
Use :meth:`parse_webhook` for webhook handlers (parse + verify
|
|
98
|
+
in one call) or :attr:`InboundEmail.unverified_payload` for
|
|
99
|
+
inspection without verification. ``parse`` will be removed in
|
|
100
|
+
3.0.
|
|
101
|
+
|
|
95
102
|
Accepts bytes, JSON string, dict, or a generated MessageDetail.
|
|
96
103
|
|
|
97
104
|
The returned InboundEmail starts in the *unverified* state —
|
|
98
|
-
property accesses (sender, subject, body, …)
|
|
99
|
-
:class:`UnverifiedEmailError` until
|
|
100
|
-
|
|
101
|
-
|
|
105
|
+
property accesses (sender, subject, body, …) raise
|
|
106
|
+
:class:`UnverifiedEmailError` until :meth:`InboundEmail.verify_signature`
|
|
107
|
+
succeeds. The combination of "looks usable" + "blows up on first
|
|
108
|
+
field access" is precisely the trap that motivated the deprecation;
|
|
109
|
+
``parse_webhook`` raises immediately on bad signatures and returns
|
|
110
|
+
a ready-to-use object on success.
|
|
102
111
|
"""
|
|
112
|
+
warnings.warn(
|
|
113
|
+
"E2AClient.parse() is deprecated and will be removed in 3.0. "
|
|
114
|
+
"For webhook handlers, use client.parse_webhook(body) — it "
|
|
115
|
+
"parses and HMAC-verifies in one call. For inspection without "
|
|
116
|
+
"verification, use email.unverified_payload after parse_webhook.",
|
|
117
|
+
DeprecationWarning,
|
|
118
|
+
stacklevel=2,
|
|
119
|
+
)
|
|
120
|
+
return self._parse_unverified(body)
|
|
121
|
+
|
|
122
|
+
def _parse_unverified(
|
|
123
|
+
self,
|
|
124
|
+
body: bytes | str | dict[str, Any] | MessageDetail,
|
|
125
|
+
) -> InboundEmail:
|
|
126
|
+
"""Internal parse without the deprecation warning. ``parse_webhook``
|
|
127
|
+
delegates here so the recommended path doesn't emit the warning
|
|
128
|
+
meant for direct ``parse`` callers."""
|
|
103
129
|
if isinstance(body, MessageDetail):
|
|
104
130
|
data = body.model_dump(by_alias=True)
|
|
105
131
|
elif isinstance(body, dict):
|
|
@@ -126,7 +152,7 @@ class E2AClient:
|
|
|
126
152
|
``secret`` defaults to the ``E2A_WEBHOOK_SECRET`` environment
|
|
127
153
|
variable (with ``E2A_HMAC_SECRET`` accepted as a deprecated alias).
|
|
128
154
|
"""
|
|
129
|
-
email = self.
|
|
155
|
+
email = self._parse_unverified(body)
|
|
130
156
|
if not email.verify_signature(secret):
|
|
131
157
|
raise PermissionError("HMAC signature verification failed")
|
|
132
158
|
return email
|
|
@@ -344,11 +344,14 @@ class InboundEmail:
|
|
|
344
344
|
|
|
345
345
|
``secret`` defaults to the ``E2A_WEBHOOK_SECRET`` environment
|
|
346
346
|
variable when omitted (with ``E2A_HMAC_SECRET`` accepted as a
|
|
347
|
-
deprecated alias)
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
347
|
+
deprecated alias).
|
|
348
|
+
|
|
349
|
+
Most webhook handlers should use :meth:`E2AClient.parse_webhook`
|
|
350
|
+
instead — it calls ``verify_signature`` internally and raises
|
|
351
|
+
``PermissionError`` on failure, so the handler reads as one
|
|
352
|
+
concise call. Use ``verify_signature`` directly only when you
|
|
353
|
+
need to inspect ``unverified_payload`` first or have some other
|
|
354
|
+
reason to keep the unverified object around.
|
|
352
355
|
|
|
353
356
|
Checks (in order):
|
|
354
357
|
|
|
@@ -121,6 +121,36 @@ async def test_parse_unsupported_type():
|
|
|
121
121
|
client.parse(12345)
|
|
122
122
|
|
|
123
123
|
|
|
124
|
+
@pytest.mark.anyio
|
|
125
|
+
async def test_parse_emits_deprecation_warning():
|
|
126
|
+
"""Mirror of the sync test — async client's `parse` must also emit
|
|
127
|
+
the deprecation warning so async webhook handlers see the same
|
|
128
|
+
migration signal."""
|
|
129
|
+
webhook = _make_message_detail_json()
|
|
130
|
+
async with AsyncE2AClient(api_key="k", agent_email="bot@agents.e2a.dev") as client:
|
|
131
|
+
with pytest.warns(DeprecationWarning, match="parse_webhook"):
|
|
132
|
+
client.parse(webhook)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@pytest.mark.anyio
|
|
136
|
+
async def test_parse_webhook_does_not_emit_deprecation_warning(monkeypatch):
|
|
137
|
+
"""Same regression guard as the sync test — `parse_webhook` must not
|
|
138
|
+
surface the deprecation warning meant for direct `parse` callers."""
|
|
139
|
+
import warnings
|
|
140
|
+
|
|
141
|
+
monkeypatch.setenv("E2A_WEBHOOK_SECRET", "secret-xyz")
|
|
142
|
+
webhook = _make_message_detail_json()
|
|
143
|
+
async with AsyncE2AClient(api_key="k", agent_email="bot@agents.e2a.dev") as client:
|
|
144
|
+
with warnings.catch_warnings():
|
|
145
|
+
warnings.simplefilter("error")
|
|
146
|
+
try:
|
|
147
|
+
client.parse_webhook(webhook)
|
|
148
|
+
except DeprecationWarning:
|
|
149
|
+
pytest.fail("parse_webhook should not emit DeprecationWarning")
|
|
150
|
+
except PermissionError:
|
|
151
|
+
pass
|
|
152
|
+
|
|
153
|
+
|
|
124
154
|
# ── get_message() ────────────────────────────────────────────────
|
|
125
155
|
|
|
126
156
|
|
|
@@ -116,6 +116,40 @@ def test_parse_unsupported_type():
|
|
|
116
116
|
client.parse(12345)
|
|
117
117
|
|
|
118
118
|
|
|
119
|
+
def test_parse_emits_deprecation_warning():
|
|
120
|
+
"""Calling the legacy `parse` method on the sync client emits a
|
|
121
|
+
DeprecationWarning that points users at parse_webhook. Pin the
|
|
122
|
+
deprecation contract so it isn't silently dropped before 3.0."""
|
|
123
|
+
webhook = _make_message_detail_json()
|
|
124
|
+
with E2AClient(api_key="k", agent_email="bot@agents.e2a.dev") as client:
|
|
125
|
+
with pytest.warns(DeprecationWarning, match="parse_webhook"):
|
|
126
|
+
client.parse(webhook)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_parse_webhook_does_not_emit_deprecation_warning(monkeypatch):
|
|
130
|
+
"""The recommended path must not emit the deprecation warning even
|
|
131
|
+
though it shares the underlying parse logic. Regression: an early
|
|
132
|
+
refactor had parse_webhook delegate to parse(), which made every
|
|
133
|
+
correct caller see the deprecation message."""
|
|
134
|
+
import warnings
|
|
135
|
+
|
|
136
|
+
monkeypatch.setenv("E2A_WEBHOOK_SECRET", "secret-xyz")
|
|
137
|
+
webhook = _make_message_detail_json()
|
|
138
|
+
with E2AClient(api_key="k", agent_email="bot@agents.e2a.dev") as client:
|
|
139
|
+
with warnings.catch_warnings():
|
|
140
|
+
warnings.simplefilter("error")
|
|
141
|
+
try:
|
|
142
|
+
client.parse_webhook(webhook)
|
|
143
|
+
except DeprecationWarning:
|
|
144
|
+
pytest.fail("parse_webhook should not emit DeprecationWarning")
|
|
145
|
+
except PermissionError:
|
|
146
|
+
# Expected — fixture body has no real signature, so
|
|
147
|
+
# verify_signature returns False and parse_webhook raises.
|
|
148
|
+
# The point of this test is the absence of DeprecationWarning,
|
|
149
|
+
# which we'd have hit before the PermissionError if present.
|
|
150
|
+
pass
|
|
151
|
+
|
|
152
|
+
|
|
119
153
|
# ── get_message() ────────────────────────────────────────────────
|
|
120
154
|
|
|
121
155
|
|
|
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
|
{e2a-2.1.0 → e2a-2.2.0}/uv.lock
RENAMED
|
File without changes
|