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.
Files changed (27) hide show
  1. {e2a-2.1.0 → e2a-2.2.0}/PKG-INFO +2 -2
  2. {e2a-2.1.0 → e2a-2.2.0}/README.md +1 -1
  3. {e2a-2.1.0 → e2a-2.2.0}/pyproject.toml +1 -1
  4. {e2a-2.1.0 → e2a-2.2.0}/src/e2a/v1/async_client.py +24 -3
  5. {e2a-2.1.0 → e2a-2.2.0}/src/e2a/v1/client.py +31 -5
  6. {e2a-2.1.0 → e2a-2.2.0}/src/e2a/v1/handler.py +8 -5
  7. {e2a-2.1.0 → e2a-2.2.0}/tests/test_v1_async_client.py +30 -0
  8. {e2a-2.1.0 → e2a-2.2.0}/tests/test_v1_client.py +34 -0
  9. {e2a-2.1.0 → e2a-2.2.0}/.gitignore +0 -0
  10. {e2a-2.1.0 → e2a-2.2.0}/codegen-requirements.txt +0 -0
  11. {e2a-2.1.0 → e2a-2.2.0}/src/e2a/__init__.py +0 -0
  12. {e2a-2.1.0 → e2a-2.2.0}/src/e2a/v1/__init__.py +0 -0
  13. {e2a-2.1.0 → e2a-2.2.0}/src/e2a/v1/api.py +0 -0
  14. {e2a-2.1.0 → e2a-2.2.0}/src/e2a/v1/generated/__init__.py +0 -0
  15. {e2a-2.1.0 → e2a-2.2.0}/src/e2a/v1/generated/_internal.py +0 -0
  16. {e2a-2.1.0 → e2a-2.2.0}/src/e2a/v1/generated/github_com_Mnexa_AI_e2a_internal_identity.py +0 -0
  17. {e2a-2.1.0 → e2a-2.2.0}/src/e2a/v1/generated/internal_agent.py +0 -0
  18. {e2a-2.1.0 → e2a-2.2.0}/src/e2a/v1/websocket.py +0 -0
  19. {e2a-2.1.0 → e2a-2.2.0}/tests/__init__.py +0 -0
  20. {e2a-2.1.0 → e2a-2.2.0}/tests/test_contract.py +0 -0
  21. {e2a-2.1.0 → e2a-2.2.0}/tests/test_e2e.py +0 -0
  22. {e2a-2.1.0 → e2a-2.2.0}/tests/test_exports.py +0 -0
  23. {e2a-2.1.0 → e2a-2.2.0}/tests/test_generated_models.py +0 -0
  24. {e2a-2.1.0 → e2a-2.2.0}/tests/test_v1_api.py +0 -0
  25. {e2a-2.1.0 → e2a-2.2.0}/tests/test_v1_handler.py +0 -0
  26. {e2a-2.1.0 → e2a-2.2.0}/tests/test_v1_websocket.py +0 -0
  27. {e2a-2.1.0 → e2a-2.2.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: e2a
3
- Version: 2.1.0
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` — accepts bytes, str, dict, or `MessageDetail`. Returns *unverified* claim fields raise `UnverifiedEmailError` until `email.verify_signature()` succeeds.
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` — accepts bytes, str, dict, or `MessageDetail`. Returns *unverified* claim fields raise `UnverifiedEmailError` until `email.verify_signature()` succeeds.
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`
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "e2a"
7
- version = "2.1.0"
7
+ version = "2.2.0"
8
8
  description = "Python SDK for the e2a protocol — email-to-agent authentication"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -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`. For webhook handlers,
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.parse(body)
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, …) will raise
99
- :class:`UnverifiedEmailError` until you call
100
- :meth:`InboundEmail.verify_signature`. For webhook handlers,
101
- prefer :meth:`parse_webhook` which combines parse + verify.
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.parse(body)
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), so the standard webhook-handler pattern is::
348
-
349
- email = client.parse(body)
350
- if not email.verify_signature():
351
- return 401
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