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.
@@ -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
@@ -0,0 +1,7 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.11.0
4
+ hooks:
5
+ - id: ruff
6
+ args: [--fix]
7
+ - id: ruff-format
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
+ [![PyPI version](https://img.shields.io/pypi/v/pyzit-tempmail?color=1D9E75&labelColor=0f0f0f&style=flat-square)](https://pypi.org/project/pyzit-tempmail/)
56
+ [![Python](https://img.shields.io/pypi/pyversions/pyzit-tempmail?color=1D9E75&labelColor=0f0f0f&style=flat-square)](https://pypi.org/project/pyzit-tempmail/)
57
+ [![License](https://img.shields.io/pypi/l/pyzit-tempmail?color=1D9E75&labelColor=0f0f0f&style=flat-square)](LICENSE)
58
+ [![mypy](https://img.shields.io/badge/type--checked-mypy%20strict-1D9E75?labelColor=0f0f0f&style=flat-square)](https://mypy.readthedocs.io/)
59
+ [![uv](https://img.shields.io/badge/built%20with-uv-1D9E75?labelColor=0f0f0f&style=flat-square)](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>