mailflat 0.3.0__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.
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mailflat
3
- Version: 0.3.0
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
- Project-URL: Documentation, https://mailflat.net/docs
6
+ Project-URL: Documentation, https://mailflat.net
7
7
  Project-URL: Source, https://github.com/onderyentar/mailflat
8
8
  Author-email: MailFlat <support@mailflat.net>
9
9
  License: MIT
@@ -18,7 +18,7 @@ Classifier: Topic :: Communications :: Email
18
18
  Classifier: Topic :: Software Development :: Testing
19
19
  Classifier: Typing :: Typed
20
20
  Requires-Python: >=3.8
21
- Requires-Dist: httpx>=0.24
21
+ Requires-Dist: httpx<1,>=0.24
22
22
  Provides-Extra: dev
23
23
  Requires-Dist: langchain-core>=0.2; extra == 'dev'
24
24
  Requires-Dist: pytest>=7; extra == 'dev'
@@ -4,7 +4,10 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "mailflat"
7
- version = "0.3.0"
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"
@@ -21,17 +24,25 @@ classifiers = [
21
24
  "Topic :: Software Development :: Testing",
22
25
  "Typing :: Typed",
23
26
  ]
24
- dependencies = ["httpx>=0.24"]
27
+ # ÜST SINIR: httpx henüz 1.0 ÖNCESİ ve 1.0 ilan edilmiş bir kırılma eşiği. Sınırsız bırakmak
28
+ # B-047'nin (mcp>=1.2 → mcp 2.0.0 modülü kaldırdı → uvx mailflat-mcp çöktü) aynısına davetiye.
29
+ # Bugün 0.28.1 ile sorunsuz; sınır 1.0 çıkınca bilinçli olarak yükseltilecek.
30
+ dependencies = ["httpx>=0.24,<1"]
25
31
 
26
32
  [project.urls]
27
33
  Homepage = "https://mailflat.net"
28
- Documentation = "https://mailflat.net/docs"
34
+ # /docs prod'da kapalı (FastAPI Swagger yüzeyi, bkz. app/main.py::_openapi_urls).
35
+ # Public doküman sayfası açılınca burası oraya güncellenecek.
36
+ Documentation = "https://mailflat.net"
29
37
  Source = "https://github.com/onderyentar/mailflat"
30
38
 
31
39
  [project.optional-dependencies]
32
40
  langchain = ["langchain-core>=0.2"]
33
41
  dev = ["pytest>=7", "respx>=0.20", "langchain-core>=0.2"]
34
42
 
43
+ [tool.hatch.version]
44
+ path = "src/mailflat/__init__.py"
45
+
35
46
  [tool.hatch.build.targets.wheel]
36
47
  packages = ["src/mailflat"]
37
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
- __version__ = "0.3.0"
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 (StructuredTool)
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