mailflat 0.3.1__tar.gz → 0.3.3__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.
- {mailflat-0.3.1 → mailflat-0.3.3}/PKG-INFO +1 -1
- {mailflat-0.3.1 → mailflat-0.3.3}/pyproject.toml +7 -1
- {mailflat-0.3.1 → mailflat-0.3.3}/src/mailflat/__init__.py +6 -1
- {mailflat-0.3.1 → mailflat-0.3.3}/src/mailflat/langchain.py +11 -7
- mailflat-0.3.3/src/mailflat/redact.py +56 -0
- {mailflat-0.3.1 → mailflat-0.3.3}/.gitignore +0 -0
- {mailflat-0.3.1 → mailflat-0.3.3}/LICENSE +0 -0
- {mailflat-0.3.1 → mailflat-0.3.3}/README.md +0 -0
- {mailflat-0.3.1 → mailflat-0.3.3}/src/mailflat/client.py +0 -0
- {mailflat-0.3.1 → mailflat-0.3.3}/src/mailflat/errors.py +0 -0
- {mailflat-0.3.1 → mailflat-0.3.3}/src/mailflat/inbox.py +0 -0
- {mailflat-0.3.1 → mailflat-0.3.3}/src/mailflat/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mailflat
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: Official Python SDK for MailFlat — disposable, automation-friendly email inboxes with one-line OTP retrieval.
|
|
5
5
|
Project-URL: Homepage, https://mailflat.net
|
|
6
6
|
Project-URL: Documentation, https://mailflat.net
|
|
@@ -4,7 +4,10 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "mailflat"
|
|
7
|
-
|
|
7
|
+
# Sürüm TEK KAYNAKTAN okunur: src/mailflat/__init__.py::__version__ (bkz. [tool.hatch.version]).
|
|
8
|
+
# Buraya elle bir `version = ...` yazmak B-052'yi geri getirir: 0.3.1 yayımlanmışken paketin
|
|
9
|
+
# kendisi `mailflat.__version__ == "0.3.0"` diyordu. İki kaynak = eninde sonunda ayrışma.
|
|
10
|
+
dynamic = ["version"]
|
|
8
11
|
description = "Official Python SDK for MailFlat — disposable, automation-friendly email inboxes with one-line OTP retrieval."
|
|
9
12
|
readme = "README.md"
|
|
10
13
|
requires-python = ">=3.8"
|
|
@@ -37,6 +40,9 @@ Source = "https://github.com/onderyentar/mailflat"
|
|
|
37
40
|
langchain = ["langchain-core>=0.2"]
|
|
38
41
|
dev = ["pytest>=7", "respx>=0.20", "langchain-core>=0.2"]
|
|
39
42
|
|
|
43
|
+
[tool.hatch.version]
|
|
44
|
+
path = "src/mailflat/__init__.py"
|
|
45
|
+
|
|
40
46
|
[tool.hatch.build.targets.wheel]
|
|
41
47
|
packages = ["src/mailflat"]
|
|
42
48
|
|
|
@@ -11,6 +11,7 @@ Key exports:
|
|
|
11
11
|
- `Inbox`, `Message` — domain tipleri
|
|
12
12
|
- hata tipleri: `MailFlatError`, `AuthenticationError`, `PermissionError`,
|
|
13
13
|
`NotFoundError`, `RateLimitError`, `APIError`, `OTPTimeoutError`, `EncryptedInboxError`
|
|
14
|
+
- `redact_secrets()` — ajan araç çıktısından sır alanlarını atar (MCP/LangChain kullanır)
|
|
14
15
|
"""
|
|
15
16
|
from __future__ import annotations
|
|
16
17
|
|
|
@@ -26,8 +27,11 @@ from .errors import (
|
|
|
26
27
|
RateLimitError,
|
|
27
28
|
)
|
|
28
29
|
from .inbox import Inbox, Message
|
|
30
|
+
from .redact import redact_secrets
|
|
29
31
|
|
|
30
|
-
|
|
32
|
+
# Paketin TEK sürüm kaynağı — pyproject.toml bunu [tool.hatch.version] ile okur.
|
|
33
|
+
# Yayın = burayı yükselt, başka hiçbir yeri değil.
|
|
34
|
+
__version__ = "0.3.3"
|
|
31
35
|
|
|
32
36
|
__all__ = [
|
|
33
37
|
"MailFlat",
|
|
@@ -41,5 +45,6 @@ __all__ = [
|
|
|
41
45
|
"APIError",
|
|
42
46
|
"OTPTimeoutError",
|
|
43
47
|
"EncryptedInboxError",
|
|
48
|
+
"redact_secrets",
|
|
44
49
|
"__version__",
|
|
45
50
|
]
|
|
@@ -9,8 +9,11 @@ Kullanım:
|
|
|
9
9
|
tools = toolkit.get_tools() # create_inbox, list_inboxes, read_messages,
|
|
10
10
|
# wait_for_otp, send_email, delete_inbox
|
|
11
11
|
|
|
12
|
+
⚠️ Araç çıktıları `redact_secrets()`'ten geçer: per-inbox `api_key` modelin bağlamına
|
|
13
|
+
(ve oradan LangSmith trace'lerine) girmemeli — bkz. B-055.
|
|
14
|
+
|
|
12
15
|
Connected to:
|
|
13
|
-
- imports from: mailflat.client (MailFlat), langchain_core.tools
|
|
16
|
+
- imports from: mailflat.client (MailFlat), mailflat.redact, langchain_core.tools
|
|
14
17
|
- imported by: kullanıcı kodu (LangChain ajanları)
|
|
15
18
|
|
|
16
19
|
Key exports:
|
|
@@ -30,6 +33,7 @@ except ImportError as exc: # langchain-core kurulu değil → açıklayıcı ha
|
|
|
30
33
|
|
|
31
34
|
from .client import DEFAULT_BASE_URL, MailFlat
|
|
32
35
|
from .errors import EncryptedInboxError, MailFlatError, OTPTimeoutError
|
|
36
|
+
from .redact import redact_secrets
|
|
33
37
|
|
|
34
38
|
if TYPE_CHECKING:
|
|
35
39
|
from collections.abc import Sequence
|
|
@@ -73,14 +77,14 @@ class MailFlatToolkit:
|
|
|
73
77
|
label=label or None,
|
|
74
78
|
retention_hours=retention_hours if retention_hours and retention_hours > 0 else None,
|
|
75
79
|
)
|
|
76
|
-
return inbox.raw
|
|
80
|
+
return redact_secrets(inbox.raw)
|
|
77
81
|
except MailFlatError as e:
|
|
78
82
|
return {"error": str(e)}
|
|
79
83
|
|
|
80
84
|
def list_inboxes() -> dict:
|
|
81
85
|
"""List all inboxes available to this API key."""
|
|
82
86
|
try:
|
|
83
|
-
return {"ok": True, "inboxes": [i.raw for i in client.list()]}
|
|
87
|
+
return redact_secrets({"ok": True, "inboxes": [i.raw for i in client.list()]})
|
|
84
88
|
except MailFlatError as e:
|
|
85
89
|
return {"error": str(e)}
|
|
86
90
|
|
|
@@ -91,7 +95,7 @@ class MailFlatToolkit:
|
|
|
91
95
|
address: The full inbox address, e.g. signup-8f3@mailflat.net.
|
|
92
96
|
"""
|
|
93
97
|
try:
|
|
94
|
-
return {"ok": True, "emails": [m.raw for m in client.inbox(address).messages()]}
|
|
98
|
+
return redact_secrets({"ok": True, "emails": [m.raw for m in client.inbox(address).messages()]})
|
|
95
99
|
except MailFlatError as e:
|
|
96
100
|
return {"error": str(e)}
|
|
97
101
|
|
|
@@ -106,7 +110,7 @@ class MailFlatToolkit:
|
|
|
106
110
|
inbox = client.inbox(address)
|
|
107
111
|
otp = inbox.wait_for_otp(timeout=timeout)
|
|
108
112
|
latest = inbox.latest()
|
|
109
|
-
return {"otp_code": otp, "email": latest.raw if latest else None}
|
|
113
|
+
return redact_secrets({"otp_code": otp, "email": latest.raw if latest else None})
|
|
110
114
|
except OTPTimeoutError:
|
|
111
115
|
return {"otp_code": None, "error": "timeout"}
|
|
112
116
|
except EncryptedInboxError as e:
|
|
@@ -127,7 +131,7 @@ class MailFlatToolkit:
|
|
|
127
131
|
html: Optional HTML body.
|
|
128
132
|
"""
|
|
129
133
|
try:
|
|
130
|
-
return client.inbox(address).send(to, subject=subject, body=body, html=html or None)
|
|
134
|
+
return redact_secrets(client.inbox(address).send(to, subject=subject, body=body, html=html or None))
|
|
131
135
|
except MailFlatError as e:
|
|
132
136
|
return {"error": str(e)}
|
|
133
137
|
|
|
@@ -138,7 +142,7 @@ class MailFlatToolkit:
|
|
|
138
142
|
address: The inbox address to delete.
|
|
139
143
|
"""
|
|
140
144
|
try:
|
|
141
|
-
return client.inbox(address).delete()
|
|
145
|
+
return redact_secrets(client.inbox(address).delete())
|
|
142
146
|
except MailFlatError as e:
|
|
143
147
|
return {"error": str(e)}
|
|
144
148
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Ajan araçlarının çıktısından sır alanlarını temizler (B-055).
|
|
2
|
+
|
|
3
|
+
Neden var: `create_inbox` ve `list_inboxes` backend yanıtını olduğu gibi döndürüyordu ve o
|
|
4
|
+
yanıtta per-inbox `api_key` (`mf_sk_…`) vardı. Araç çıktısı **model bağlamına** giriyor;
|
|
5
|
+
oradan prompt loglarına, trace araçlarına (LangSmith, AI SDK telemetry), hata raporlarına ve
|
|
6
|
+
modelin sonraki cevaplarına geçiyor. `list_inboxes` tek çağrıda hesabın **tüm** inbox
|
|
7
|
+
anahtarlarını döküyordu.
|
|
8
|
+
|
|
9
|
+
Ayrım bilinçli:
|
|
10
|
+
- **SDK = kod yüzeyi.** `Inbox.api_key` duruyor; insanın yazdığı kod onu kullanabilir.
|
|
11
|
+
- **Tool = model yüzeyi.** Buradan geçen her şey redakte edilir.
|
|
12
|
+
|
|
13
|
+
Yöntem **isim bazlı**, değer bazlı değil: mesaj gövdesini tarayıp `mf_` ile başlayan
|
|
14
|
+
string'leri silmek, kullanıcının kendi test anahtarını konu ettiği bir maili bozardı. Alan
|
|
15
|
+
adına bakmak hem kesin hem zararsız.
|
|
16
|
+
|
|
17
|
+
Connected to:
|
|
18
|
+
- imports from: —
|
|
19
|
+
- imported by: mailflat_mcp.server, mailflat.langchain, testler
|
|
20
|
+
|
|
21
|
+
Key exports:
|
|
22
|
+
- `redact_secrets(value)` — dict/list içindeki sır alanlarını atmış kopya
|
|
23
|
+
- `SECRET_NAME_PARTS` — hangi alan adlarının sır sayıldığı
|
|
24
|
+
"""
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from typing import Any
|
|
28
|
+
|
|
29
|
+
#: Normalize edilmiş alan adı bunlardan birini İÇERİYORSA alan atılır.
|
|
30
|
+
#: Dar tutuldu: `token` bilerek YOK — domain `verify_token` gibi alanlar zararsız ve
|
|
31
|
+
#: gerektiğinde işe yarar, `otp` ise ajanın asıl istediği şey. Buradaki üçünün hiçbiri
|
|
32
|
+
#: modele bir fayda sağlamıyor.
|
|
33
|
+
SECRET_NAME_PARTS = ("apikey", "secret", "password")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _normalise(key: Any) -> str:
|
|
37
|
+
return str(key).replace("_", "").replace("-", "").lower()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _is_secret(key: Any) -> bool:
|
|
41
|
+
name = _normalise(key)
|
|
42
|
+
return any(part in name for part in SECRET_NAME_PARTS)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def redact_secrets(value: Any) -> Any:
|
|
46
|
+
"""`value`'nun sır alanları çıkarılmış kopyasını döndür (girdi değiştirilmez).
|
|
47
|
+
|
|
48
|
+
dict → sır alanları atılır, kalanlar özyinelemeli temizlenir
|
|
49
|
+
list/tuple → her eleman temizlenir (liste olarak döner, JSON'a zaten liste gidiyor)
|
|
50
|
+
diğer → olduğu gibi
|
|
51
|
+
"""
|
|
52
|
+
if isinstance(value, dict):
|
|
53
|
+
return {k: redact_secrets(v) for k, v in value.items() if not _is_secret(k)}
|
|
54
|
+
if isinstance(value, (list, tuple)):
|
|
55
|
+
return [redact_secrets(v) for v in value]
|
|
56
|
+
return value
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|