tempmail-pyzit 0.1.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.
- tempmail_pyzit-0.1.0/.gitattributes +1 -0
- tempmail_pyzit-0.1.0/.gitignore +43 -0
- tempmail_pyzit-0.1.0/.pre-commit-config.yaml +7 -0
- tempmail_pyzit-0.1.0/CHANGELOG.md +0 -0
- tempmail_pyzit-0.1.0/PKG-INFO +563 -0
- tempmail_pyzit-0.1.0/README.md +531 -0
- tempmail_pyzit-0.1.0/pyproject.toml +61 -0
- tempmail_pyzit-0.1.0/src/tempmail_pyzit/__init__.py +64 -0
- tempmail_pyzit-0.1.0/src/tempmail_pyzit/_http.py +76 -0
- tempmail_pyzit-0.1.0/src/tempmail_pyzit/async_client.py +61 -0
- tempmail_pyzit-0.1.0/src/tempmail_pyzit/client.py +95 -0
- tempmail_pyzit-0.1.0/src/tempmail_pyzit/exceptions.py +70 -0
- tempmail_pyzit-0.1.0/src/tempmail_pyzit/models.py +113 -0
- tempmail_pyzit-0.1.0/src/tempmail_pyzit/py.typed +0 -0
- tempmail_pyzit-0.1.0/tests/__init__.py +0 -0
- tempmail_pyzit-0.1.0/tests/conftest.py +15 -0
- tempmail_pyzit-0.1.0/tests/fixtures.py +70 -0
- tempmail_pyzit-0.1.0/tests/test_async_client.py +96 -0
- tempmail_pyzit-0.1.0/tests/test_client.py +192 -0
- tempmail_pyzit-0.1.0/tests/test_exceptions.py +61 -0
- tempmail_pyzit-0.1.0/tests/test_models.py +104 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
* text=auto eol=lf
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Distribution
|
|
7
|
+
build/
|
|
8
|
+
dist/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
|
|
11
|
+
# Virtual environments
|
|
12
|
+
.venv/
|
|
13
|
+
venv/
|
|
14
|
+
env/
|
|
15
|
+
|
|
16
|
+
# Testing
|
|
17
|
+
.pytest_cache/
|
|
18
|
+
.coverage
|
|
19
|
+
htmlcov/
|
|
20
|
+
|
|
21
|
+
# Type checking
|
|
22
|
+
.mypy_cache/
|
|
23
|
+
|
|
24
|
+
# Ruff
|
|
25
|
+
.ruff_cache/
|
|
26
|
+
|
|
27
|
+
# IDE
|
|
28
|
+
.vscode/
|
|
29
|
+
.idea/
|
|
30
|
+
|
|
31
|
+
# OS
|
|
32
|
+
.DS_Store
|
|
33
|
+
Thumbs.db
|
|
34
|
+
|
|
35
|
+
# Environment files
|
|
36
|
+
.env
|
|
37
|
+
.env.*
|
|
38
|
+
|
|
39
|
+
# UV
|
|
40
|
+
uv.lock
|
|
41
|
+
|
|
42
|
+
# Python version
|
|
43
|
+
.python-version
|
|
File without changes
|
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tempmail-pyzit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for the Pyzit disposable email detector API
|
|
5
|
+
Project-URL: Homepage, https://temp-mail-detector.pyzit.com
|
|
6
|
+
Project-URL: Documentation, https://temp-mail-detector.pyzit.com/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/pyzit/pyzit-tempmail-python
|
|
8
|
+
Author-email: Pyzit <hi@pyzit.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: disposable,email,tempmail,validation
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Requires-Dist: httpx>=0.27
|
|
22
|
+
Requires-Dist: pydantic>=2.0
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: build; extra == 'dev'
|
|
25
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest-asyncio; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
28
|
+
Requires-Dist: respx; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
30
|
+
Requires-Dist: twine; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
<div align="center">
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
██████╗ ██╗ ██╗███████╗██╗████████╗
|
|
37
|
+
██╔══██╗╚██╗ ██╔╝╚══███╔╝██║╚══██╔══╝
|
|
38
|
+
██████╔╝ ╚████╔╝ ███╔╝ ██║ ██║
|
|
39
|
+
██╔═══╝ ╚██╔╝ ███╔╝ ██║ ██║
|
|
40
|
+
██║ ██║ ███████╗██║ ██║
|
|
41
|
+
╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
|
|
42
|
+
|
|
43
|
+
████████╗███████╗███╗ ███╗██████╗ ███╗ ███╗ █████╗ ██╗██╗
|
|
44
|
+
╚══██╔══╝██╔════╝████╗ ████║██╔══██╗████╗ ████║██╔══██╗██║██║
|
|
45
|
+
██║ █████╗ ██╔████╔██║██████╔╝██╔████╔██║███████║██║██║
|
|
46
|
+
██║ ██╔══╝ ██║╚██╔╝██║██╔═══╝ ██║╚██╔╝██║██╔══██║██║██║
|
|
47
|
+
██║ ███████╗██║ ╚═╝ ██║██║ ██║ ╚═╝ ██║██║ ██║██║███████╗
|
|
48
|
+
╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚══════╝
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**The official Python SDK for the [Pyzit Disposable Email Detector API](https://temp-mail-detector.pyzit.com)**
|
|
52
|
+
|
|
53
|
+
Stop fake signups. Block throwaway addresses. Protect your platform.
|
|
54
|
+
|
|
55
|
+
[](https://pypi.org/project/pyzit-tempmail/)
|
|
56
|
+
[](https://pypi.org/project/pyzit-tempmail/)
|
|
57
|
+
[](LICENSE)
|
|
58
|
+
[](https://mypy.readthedocs.io/)
|
|
59
|
+
[](https://github.com/astral-sh/uv)
|
|
60
|
+
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## ◈ What is this?
|
|
66
|
+
|
|
67
|
+
`pyzit-tempmail` is the official Python client for the **Pyzit Disposable Email API** — a service that detects throwaway, temporary, and fake email addresses in real time.
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
User submits email SDK validates it Your app decides
|
|
71
|
+
───────────────── ───────────────── ───────────────
|
|
72
|
+
user@mailnator.com ──────► is_disposable: True ──────► ✗ Reject signup
|
|
73
|
+
hi@yourcompany.com ──────► is_disposable: False ──────► ✓ Allow signup
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Works with **Django**, **Flask**, **FastAPI**, plain scripts — sync and async both supported out of the box.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## ◈ Table of contents
|
|
81
|
+
|
|
82
|
+
- [Installation](#-installation)
|
|
83
|
+
- [Quick start](#-quick-start)
|
|
84
|
+
- [API tiers](#-api-tiers)
|
|
85
|
+
- [All methods](#-all-methods)
|
|
86
|
+
- [check()](#checkresult--checkemail-str)
|
|
87
|
+
- [detailed()](#detailedresult--detailedemail-str)
|
|
88
|
+
- [bulk()](#bulkresult--bulkemails-liststr)
|
|
89
|
+
- [Async usage](#-async-usage)
|
|
90
|
+
- [Response models](#-response-models)
|
|
91
|
+
- [Error handling](#-error-handling)
|
|
92
|
+
- [Framework integrations](#-framework-integrations)
|
|
93
|
+
- [Configuration](#-configuration)
|
|
94
|
+
- [Development](#-development)
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## ◈ Installation
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
pip install pyzit-tempmail
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Or with [uv](https://github.com/astral-sh/uv) (recommended):
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
uv add pyzit-tempmail
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Requirements:** Python 3.9 or higher.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## ◈ Quick start
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from pyzit_tempmail import TempMailClient
|
|
118
|
+
|
|
119
|
+
client = TempMailClient("YOUR_API_TOKEN")
|
|
120
|
+
|
|
121
|
+
result = client.check("user@example.com")
|
|
122
|
+
|
|
123
|
+
if result.is_disposable:
|
|
124
|
+
print("❌ Disposable email — rejected")
|
|
125
|
+
else:
|
|
126
|
+
print("✅ Looks clean — allowed")
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Get your API token at [temp-mail-detector.pyzit.com](https://temp-mail-detector.pyzit.com).
|
|
130
|
+
|
|
131
|
+
> **Tip:** Store your token in an environment variable, never hard-code it.
|
|
132
|
+
> ```python
|
|
133
|
+
> import os
|
|
134
|
+
> client = TempMailClient(os.environ["PYZIT_TOKEN"])
|
|
135
|
+
> ```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## ◈ API tiers
|
|
140
|
+
|
|
141
|
+
Three endpoints, three plan levels. Use only what you need.
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
145
|
+
│ ENDPOINT OVERVIEW │
|
|
146
|
+
├──────────────────┬─────────────┬──────────────────────────────────┤
|
|
147
|
+
│ Method │ Plan │ What you get │
|
|
148
|
+
├──────────────────┼─────────────┼──────────────────────────────────┤
|
|
149
|
+
│ client.check() │ Free │ is_disposable + status │
|
|
150
|
+
│ client.detailed()│ Pro │ DNS, signals, risk score, reco │
|
|
151
|
+
│ client.bulk() │ Business │ Up to 100 emails, one request │
|
|
152
|
+
└──────────────────┴─────────────┴──────────────────────────────────┘
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## ◈ All methods
|
|
158
|
+
|
|
159
|
+
### `CheckResult` ← `check(email: str)`
|
|
160
|
+
|
|
161
|
+
The fastest check. One email in, one decision out. **Free tier.**
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
result = client.check("user@mailnator.com")
|
|
165
|
+
|
|
166
|
+
result.email # "user@mailnator.com"
|
|
167
|
+
result.is_disposable # True
|
|
168
|
+
result.status # "disposable"
|
|
169
|
+
result.is_clean # False (convenience property, opposite of is_disposable)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Response shape:**
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
CheckResult
|
|
176
|
+
├── email str — the email you submitted
|
|
177
|
+
├── is_disposable bool — True = block it
|
|
178
|
+
├── status str — "clean" | "disposable"
|
|
179
|
+
└── is_clean bool — shorthand for not is_disposable
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
### `DetailedResult` ← `detailed(email: str)`
|
|
185
|
+
|
|
186
|
+
Full forensic analysis. DNS records, SMTP probing, reputation scoring,
|
|
187
|
+
domain age, signal breakdown. **Pro tier.**
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
result = client.detailed("suspicious@new-domain.io")
|
|
191
|
+
|
|
192
|
+
result.risk_level # "high"
|
|
193
|
+
result.recommendation # "reject"
|
|
194
|
+
result.reputation_score # 0.0
|
|
195
|
+
result.should_reject # True (convenience property)
|
|
196
|
+
|
|
197
|
+
# DNS intelligence
|
|
198
|
+
result.details.dns_intelligence.has_mx # False
|
|
199
|
+
result.details.dns_intelligence.has_spf # False
|
|
200
|
+
|
|
201
|
+
# Signal breakdown
|
|
202
|
+
result.details.signals.negative # ["no_mx_records", "new_domain", ...]
|
|
203
|
+
result.details.signals.positive # []
|
|
204
|
+
|
|
205
|
+
# Domain stability
|
|
206
|
+
result.details.stability.domain_age_days # 0
|
|
207
|
+
result.details.stability.is_new_domain # True
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Response shape:**
|
|
211
|
+
|
|
212
|
+
```
|
|
213
|
+
DetailedResult
|
|
214
|
+
├── email str
|
|
215
|
+
├── domain str
|
|
216
|
+
├── is_disposable bool
|
|
217
|
+
├── status str
|
|
218
|
+
├── reputation_score float — 0.0 (worst) to 1.0 (best)
|
|
219
|
+
├── risk_level str — "low" | "medium" | "high"
|
|
220
|
+
├── recommendation str — "accept" | "review" | "reject"
|
|
221
|
+
├── should_reject bool — shorthand: recommendation == "reject"
|
|
222
|
+
└── details
|
|
223
|
+
├── reputation
|
|
224
|
+
│ ├── reputation_score float
|
|
225
|
+
│ ├── is_disposable bool
|
|
226
|
+
│ ├── disposable_confidence float
|
|
227
|
+
│ ├── risk_level str
|
|
228
|
+
│ └── recommendation str
|
|
229
|
+
├── signals
|
|
230
|
+
│ ├── positive list[str] — trust signals
|
|
231
|
+
│ ├── negative list[str] — risk signals
|
|
232
|
+
│ └── neutral list[str] — ambiguous signals
|
|
233
|
+
├── dns_intelligence
|
|
234
|
+
│ ├── has_mx bool
|
|
235
|
+
│ ├── mx_records list[str]
|
|
236
|
+
│ ├── has_a_record bool
|
|
237
|
+
│ ├── has_spf bool
|
|
238
|
+
│ ├── has_dmarc bool
|
|
239
|
+
│ └── error str | None
|
|
240
|
+
└── stability
|
|
241
|
+
├── stability_score float
|
|
242
|
+
├── domain_age_days int
|
|
243
|
+
├── is_new_domain bool
|
|
244
|
+
└── risk_factors list[str]
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
### `BulkResult` ← `bulk(emails: list[str])`
|
|
250
|
+
|
|
251
|
+
Validate up to 100 emails in a single API call. **Business tier.**
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
result = client.bulk([
|
|
255
|
+
"hi@pyzit.com",
|
|
256
|
+
"cyz@temp-mail.org",
|
|
257
|
+
"user@mailnator.com",
|
|
258
|
+
])
|
|
259
|
+
|
|
260
|
+
result.processed # 3
|
|
261
|
+
result.results # {"hi@pyzit.com": False, "cyz@temp-mail.org": True, ...}
|
|
262
|
+
|
|
263
|
+
result.disposable_emails() # ["cyz@temp-mail.org", "user@mailnator.com"]
|
|
264
|
+
result.clean_emails() # ["hi@pyzit.com"]
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**Response shape:**
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
BulkResult
|
|
271
|
+
├── results dict[str, bool] — {email: is_disposable}
|
|
272
|
+
├── processed int — number of emails processed
|
|
273
|
+
├── disposable_emails() list[str] — emails where is_disposable=True
|
|
274
|
+
└── clean_emails() list[str] — emails where is_disposable=False
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## ◈ Async usage
|
|
280
|
+
|
|
281
|
+
`AsyncTempMailClient` is a drop-in replacement for async codebases.
|
|
282
|
+
The method signatures are identical — just add `await`.
|
|
283
|
+
|
|
284
|
+
```python
|
|
285
|
+
from pyzit_tempmail import AsyncTempMailClient
|
|
286
|
+
|
|
287
|
+
client = AsyncTempMailClient("YOUR_API_TOKEN")
|
|
288
|
+
|
|
289
|
+
# anywhere inside an async function:
|
|
290
|
+
result = await client.check("user@example.com")
|
|
291
|
+
result = await client.detailed("user@example.com")
|
|
292
|
+
result = await client.bulk(["a@x.com", "b@y.com"])
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Both clients share the same response models, exceptions, and behaviour.
|
|
296
|
+
There is no functional difference other than `async/await`.
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## ◈ Response models
|
|
301
|
+
|
|
302
|
+
All responses are fully typed [Pydantic v2](https://docs.pydantic.dev/) models.
|
|
303
|
+
You get autocomplete, runtime validation, and type safety for free.
|
|
304
|
+
|
|
305
|
+
```
|
|
306
|
+
pyzit_tempmail.models
|
|
307
|
+
│
|
|
308
|
+
├── CheckResult ← client.check()
|
|
309
|
+
├── DetailedResult ← client.detailed()
|
|
310
|
+
│ └── DetailedDetails
|
|
311
|
+
│ ├── ReputationDetail
|
|
312
|
+
│ ├── Signals
|
|
313
|
+
│ ├── DnsIntelligence
|
|
314
|
+
│ └── StabilityInfo
|
|
315
|
+
└── BulkResult ← client.bulk()
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## ◈ Error handling
|
|
321
|
+
|
|
322
|
+
All SDK errors inherit from `PyzitError`, so you can catch everything
|
|
323
|
+
at one level or be specific.
|
|
324
|
+
|
|
325
|
+
```
|
|
326
|
+
PyzitError ← catch-all for any SDK error
|
|
327
|
+
├── AuthenticationError ← HTTP 401 — bad or missing token
|
|
328
|
+
├── PlanRequiredError ← HTTP 402/403 — need a higher plan
|
|
329
|
+
│ └── .required_plan ← str, e.g. "pro" or "business"
|
|
330
|
+
├── RateLimitError ← HTTP 429 — slow down
|
|
331
|
+
│ └── .retry_after ← int, seconds to wait
|
|
332
|
+
├── APIError ← any other non-2xx response
|
|
333
|
+
│ ├── .status_code ← int
|
|
334
|
+
│ └── .response_body ← str, raw body for debugging
|
|
335
|
+
└── TimeoutError ← request took too long
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
**Recommended pattern — catch specific errors:**
|
|
339
|
+
|
|
340
|
+
```python
|
|
341
|
+
from pyzit_tempmail import (
|
|
342
|
+
TempMailClient,
|
|
343
|
+
AuthenticationError,
|
|
344
|
+
PlanRequiredError,
|
|
345
|
+
RateLimitError,
|
|
346
|
+
APIError,
|
|
347
|
+
TimeoutError,
|
|
348
|
+
PyzitError,
|
|
349
|
+
)
|
|
350
|
+
import time
|
|
351
|
+
|
|
352
|
+
client = TempMailClient("YOUR_TOKEN")
|
|
353
|
+
|
|
354
|
+
try:
|
|
355
|
+
result = client.check("user@example.com")
|
|
356
|
+
|
|
357
|
+
except AuthenticationError:
|
|
358
|
+
# token is wrong — fail fast, don't retry
|
|
359
|
+
raise SystemExit("Check your PYZIT_TOKEN environment variable.")
|
|
360
|
+
|
|
361
|
+
except PlanRequiredError as e:
|
|
362
|
+
# you called an endpoint above your plan
|
|
363
|
+
print(f"Upgrade to {e.required_plan} to use this feature.")
|
|
364
|
+
|
|
365
|
+
except RateLimitError as e:
|
|
366
|
+
# back off and retry
|
|
367
|
+
time.sleep(e.retry_after)
|
|
368
|
+
result = client.check("user@example.com")
|
|
369
|
+
|
|
370
|
+
except TimeoutError:
|
|
371
|
+
# API too slow — decide how to handle in your app
|
|
372
|
+
print("Pyzit API timed out — allowing email through.")
|
|
373
|
+
|
|
374
|
+
except APIError as e:
|
|
375
|
+
# unexpected server error
|
|
376
|
+
print(f"Unexpected error {e.status_code}: {e.response_body}")
|
|
377
|
+
|
|
378
|
+
except PyzitError:
|
|
379
|
+
# fallback for any other SDK error
|
|
380
|
+
print("Something went wrong with the Pyzit SDK.")
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
**Minimal pattern — just block disposables, ignore errors gracefully:**
|
|
384
|
+
|
|
385
|
+
```python
|
|
386
|
+
def is_allowed(email: str) -> bool:
|
|
387
|
+
try:
|
|
388
|
+
return not client.check(email).is_disposable
|
|
389
|
+
except PyzitError:
|
|
390
|
+
return True # fail open — let the email through if API is down
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## ◈ Framework integrations
|
|
396
|
+
|
|
397
|
+
### Django
|
|
398
|
+
|
|
399
|
+
```python
|
|
400
|
+
# validators.py
|
|
401
|
+
from django.core.exceptions import ValidationError
|
|
402
|
+
from pyzit_tempmail import TempMailClient, PyzitError
|
|
403
|
+
import os
|
|
404
|
+
|
|
405
|
+
_client = TempMailClient(os.environ["PYZIT_TOKEN"])
|
|
406
|
+
|
|
407
|
+
def validate_no_disposable_email(value: str) -> None:
|
|
408
|
+
try:
|
|
409
|
+
result = _client.check(value)
|
|
410
|
+
if result.is_disposable:
|
|
411
|
+
raise ValidationError(
|
|
412
|
+
"Disposable email addresses are not allowed. "
|
|
413
|
+
"Please use your real email."
|
|
414
|
+
)
|
|
415
|
+
except PyzitError:
|
|
416
|
+
pass # fail open if API is unreachable
|
|
417
|
+
|
|
418
|
+
# models.py
|
|
419
|
+
from django.db import models
|
|
420
|
+
from .validators import validate_no_disposable_email
|
|
421
|
+
|
|
422
|
+
class UserProfile(models.Model):
|
|
423
|
+
email = models.EmailField(validators=[validate_no_disposable_email])
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
### FastAPI
|
|
429
|
+
|
|
430
|
+
```python
|
|
431
|
+
from fastapi import FastAPI, HTTPException
|
|
432
|
+
from pyzit_tempmail import AsyncTempMailClient, PyzitError
|
|
433
|
+
import os
|
|
434
|
+
|
|
435
|
+
app = FastAPI()
|
|
436
|
+
client = AsyncTempMailClient(os.environ["PYZIT_TOKEN"])
|
|
437
|
+
|
|
438
|
+
@app.post("/register")
|
|
439
|
+
async def register(email: str):
|
|
440
|
+
try:
|
|
441
|
+
result = await client.check(email)
|
|
442
|
+
except PyzitError:
|
|
443
|
+
pass # fail open
|
|
444
|
+
else:
|
|
445
|
+
if result.is_disposable:
|
|
446
|
+
raise HTTPException(
|
|
447
|
+
status_code=422,
|
|
448
|
+
detail="Disposable email addresses are not permitted."
|
|
449
|
+
)
|
|
450
|
+
return {"status": "ok", "email": email}
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
### Flask
|
|
456
|
+
|
|
457
|
+
```python
|
|
458
|
+
from flask import Flask, request, jsonify
|
|
459
|
+
from pyzit_tempmail import TempMailClient, PyzitError
|
|
460
|
+
import os
|
|
461
|
+
|
|
462
|
+
app = Flask(__name__)
|
|
463
|
+
client = TempMailClient(os.environ["PYZIT_TOKEN"])
|
|
464
|
+
|
|
465
|
+
@app.route("/register", methods=["POST"])
|
|
466
|
+
def register():
|
|
467
|
+
email = request.json.get("email", "")
|
|
468
|
+
try:
|
|
469
|
+
result = client.check(email)
|
|
470
|
+
if result.is_disposable:
|
|
471
|
+
return jsonify({"error": "Disposable emails not allowed."}), 422
|
|
472
|
+
except PyzitError:
|
|
473
|
+
pass # fail open
|
|
474
|
+
return jsonify({"status": "ok"})
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
---
|
|
478
|
+
|
|
479
|
+
## ◈ Configuration
|
|
480
|
+
|
|
481
|
+
Both `TempMailClient` and `AsyncTempMailClient` accept the same options:
|
|
482
|
+
|
|
483
|
+
```python
|
|
484
|
+
client = TempMailClient(
|
|
485
|
+
api_token = "YOUR_TOKEN", # required
|
|
486
|
+
timeout = 10.0, # seconds, default 10.0
|
|
487
|
+
base_url = "https://api-tempmail.pyzit.com/v1", # override for testing
|
|
488
|
+
)
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
| Parameter | Type | Default | Description |
|
|
492
|
+
|-------------|---------|----------------------------------------------|------------------------------------|
|
|
493
|
+
| `api_token` | `str` | — | Your Pyzit API token (required) |
|
|
494
|
+
| `timeout` | `float` | `10.0` | Request timeout in seconds |
|
|
495
|
+
| `base_url` | `str` | `https://api-tempmail.pyzit.com/v1` | Override for local testing / mocks |
|
|
496
|
+
|
|
497
|
+
---
|
|
498
|
+
|
|
499
|
+
## ◈ Development
|
|
500
|
+
|
|
501
|
+
This project is built with [uv](https://github.com/astral-sh/uv).
|
|
502
|
+
|
|
503
|
+
```bash
|
|
504
|
+
# clone
|
|
505
|
+
git clone https://github.com/pyzit/pyzit-tempmail-python.git
|
|
506
|
+
cd pyzit-tempmail-python
|
|
507
|
+
|
|
508
|
+
# install all dev dependencies
|
|
509
|
+
uv sync --extra dev
|
|
510
|
+
|
|
511
|
+
# run tests (no real API calls — all HTTP is mocked)
|
|
512
|
+
uv run pytest -v
|
|
513
|
+
|
|
514
|
+
# lint
|
|
515
|
+
uv run ruff check . --fix
|
|
516
|
+
uv run ruff format .
|
|
517
|
+
|
|
518
|
+
# type check (strict mypy)
|
|
519
|
+
uv run mypy src/
|
|
520
|
+
|
|
521
|
+
# build distribution
|
|
522
|
+
uv build
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
**Project layout:**
|
|
526
|
+
|
|
527
|
+
```
|
|
528
|
+
src/pyzit_tempmail/
|
|
529
|
+
├── __init__.py ← public API surface
|
|
530
|
+
├── exceptions.py ← all custom error classes
|
|
531
|
+
├── models.py ← pydantic response types
|
|
532
|
+
├── _http.py ← shared transport (headers, error handling)
|
|
533
|
+
├── client.py ← TempMailClient (sync)
|
|
534
|
+
└── async_client.py ← AsyncTempMailClient (async)
|
|
535
|
+
|
|
536
|
+
tests/
|
|
537
|
+
├── conftest.py ← shared fixtures
|
|
538
|
+
├── test_client.py ← sync client tests
|
|
539
|
+
├── test_async_client.py
|
|
540
|
+
└── test_models.py
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
---
|
|
544
|
+
|
|
545
|
+
## ◈ Changelog
|
|
546
|
+
|
|
547
|
+
See [CHANGELOG.md](CHANGELOG.md).
|
|
548
|
+
|
|
549
|
+
---
|
|
550
|
+
|
|
551
|
+
## ◈ License
|
|
552
|
+
|
|
553
|
+
[MIT](LICENSE) © [Pyzit](https://pyzit.com)
|
|
554
|
+
|
|
555
|
+
---
|
|
556
|
+
|
|
557
|
+
<div align="center">
|
|
558
|
+
|
|
559
|
+
```
|
|
560
|
+
Built with care by the Pyzit team · https://pyzit.com
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
</div>
|