checkharbor 0.1.0__py3-none-any.whl
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.
checkharbor/__init__.py
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Checkharbor Python SDK — single-file, zero-dependency beyond `requests`.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
from checkharbor import Checkharbor, CheckharborError
|
|
6
|
+
|
|
7
|
+
client = Checkharbor(api_key="chk_live_...")
|
|
8
|
+
result = client.validate_email("john@example.com")
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import csv
|
|
14
|
+
import io
|
|
15
|
+
import time
|
|
16
|
+
from typing import Any, Optional, Union
|
|
17
|
+
|
|
18
|
+
import requests
|
|
19
|
+
|
|
20
|
+
__version__ = "0.1.0"
|
|
21
|
+
__all__ = ["Checkharbor", "CheckharborError"]
|
|
22
|
+
|
|
23
|
+
DEFAULT_BASE_URL = "https://api.checkharbor.com"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class CheckharborError(Exception):
|
|
27
|
+
"""Raised for any non-2xx response from the Checkharbor API."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, code: str, message: str, status: int) -> None:
|
|
30
|
+
super().__init__(message)
|
|
31
|
+
self.code = code
|
|
32
|
+
self.status = status
|
|
33
|
+
|
|
34
|
+
def __repr__(self) -> str:
|
|
35
|
+
return f"CheckharborError(code={self.code!r}, status={self.status}, message={str(self)!r})"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class _BatchNamespace:
|
|
39
|
+
def __init__(self, client: "Checkharbor") -> None:
|
|
40
|
+
self._client = client
|
|
41
|
+
|
|
42
|
+
def create(
|
|
43
|
+
self,
|
|
44
|
+
*,
|
|
45
|
+
type: str,
|
|
46
|
+
rows: Optional[list[str]] = None,
|
|
47
|
+
csv_string: Optional[str] = None,
|
|
48
|
+
webhook_url: Optional[str] = None,
|
|
49
|
+
) -> dict[str, Any]:
|
|
50
|
+
"""
|
|
51
|
+
Create a batch job.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
type: "email", "phone", or "ip"
|
|
55
|
+
rows: list of values (will be joined as CSV)
|
|
56
|
+
csv_string: raw CSV content (alternative to rows)
|
|
57
|
+
webhook_url: optional callback URL when job completes
|
|
58
|
+
"""
|
|
59
|
+
if csv_string is None and not rows:
|
|
60
|
+
raise ValueError("batch.create() requires either rows or csv_string")
|
|
61
|
+
|
|
62
|
+
content = csv_string if csv_string is not None else "\n".join(rows) # type: ignore[arg-type]
|
|
63
|
+
|
|
64
|
+
files = {"file": ("data.csv", io.BytesIO(content.encode()), "text/csv")}
|
|
65
|
+
data: dict[str, str] = {"type": type}
|
|
66
|
+
if webhook_url:
|
|
67
|
+
data["webhook_url"] = webhook_url
|
|
68
|
+
|
|
69
|
+
return self._client._request("POST", "/v1/batch", data=data, files=files)
|
|
70
|
+
|
|
71
|
+
def get(self, job_id: str) -> dict[str, Any]:
|
|
72
|
+
"""Fetch the status of a batch job."""
|
|
73
|
+
return self._client._request("GET", f"/v1/batch/{job_id}")
|
|
74
|
+
|
|
75
|
+
def wait_until_done(
|
|
76
|
+
self,
|
|
77
|
+
job_id: str,
|
|
78
|
+
*,
|
|
79
|
+
poll_seconds: float = 2.0,
|
|
80
|
+
timeout_seconds: float = 300.0,
|
|
81
|
+
) -> dict[str, Any]:
|
|
82
|
+
"""
|
|
83
|
+
Block until the batch job is done or failed.
|
|
84
|
+
|
|
85
|
+
Raises:
|
|
86
|
+
CheckharborError: if job fails or timeout is exceeded.
|
|
87
|
+
"""
|
|
88
|
+
deadline = time.monotonic() + timeout_seconds
|
|
89
|
+
while True:
|
|
90
|
+
job = self.get(job_id)
|
|
91
|
+
status = job.get("status")
|
|
92
|
+
if status == "done":
|
|
93
|
+
return job
|
|
94
|
+
if status == "failed":
|
|
95
|
+
raise CheckharborError("batch_failed", f"Batch job {job_id} failed", 200)
|
|
96
|
+
if time.monotonic() >= deadline:
|
|
97
|
+
raise CheckharborError(
|
|
98
|
+
"timeout",
|
|
99
|
+
f"Batch job {job_id} timed out after {timeout_seconds}s",
|
|
100
|
+
0,
|
|
101
|
+
)
|
|
102
|
+
time.sleep(poll_seconds)
|
|
103
|
+
|
|
104
|
+
def download_result(self, job: dict[str, Any]) -> str:
|
|
105
|
+
"""
|
|
106
|
+
Download result CSV from a completed batch job.
|
|
107
|
+
The result_url is a signed URL — no auth header needed.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
CSV content as a string.
|
|
111
|
+
"""
|
|
112
|
+
if job.get("status") != "done":
|
|
113
|
+
raise CheckharborError("not_done", "Job is not done yet", 0)
|
|
114
|
+
result_url = job.get("result_url")
|
|
115
|
+
if not result_url:
|
|
116
|
+
raise CheckharborError("no_result_url", "Job has no result_url", 0)
|
|
117
|
+
resp = requests.get(result_url, timeout=30)
|
|
118
|
+
if not resp.ok:
|
|
119
|
+
raise CheckharborError("download_failed", f"Download failed: HTTP {resp.status_code}", resp.status_code)
|
|
120
|
+
return resp.text
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class Checkharbor:
|
|
124
|
+
"""
|
|
125
|
+
Checkharbor API client.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
api_key: Your Checkharbor API key (X-Api-Key header).
|
|
129
|
+
base_url: Override the default API base URL.
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
def __init__(
|
|
133
|
+
self,
|
|
134
|
+
api_key: str,
|
|
135
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
136
|
+
) -> None:
|
|
137
|
+
if not api_key:
|
|
138
|
+
raise ValueError("api_key is required")
|
|
139
|
+
self._api_key = api_key
|
|
140
|
+
self._base_url = base_url.rstrip("/")
|
|
141
|
+
self._session = requests.Session()
|
|
142
|
+
self._session.headers["X-Api-Key"] = api_key
|
|
143
|
+
self.batch = _BatchNamespace(self)
|
|
144
|
+
|
|
145
|
+
def _request(
|
|
146
|
+
self,
|
|
147
|
+
method: str,
|
|
148
|
+
path: str,
|
|
149
|
+
json: Optional[dict[str, Any]] = None,
|
|
150
|
+
data: Optional[dict[str, str]] = None,
|
|
151
|
+
files: Optional[Any] = None,
|
|
152
|
+
) -> dict[str, Any]:
|
|
153
|
+
url = f"{self._base_url}{path}"
|
|
154
|
+
resp = self._session.request(method, url, json=json, data=data, files=files, timeout=30)
|
|
155
|
+
if not resp.ok:
|
|
156
|
+
code = "api_error"
|
|
157
|
+
message = f"HTTP {resp.status_code}"
|
|
158
|
+
try:
|
|
159
|
+
body = resp.json()
|
|
160
|
+
code = body.get("error", {}).get("code", code)
|
|
161
|
+
message = body.get("error", {}).get("message", message)
|
|
162
|
+
except Exception:
|
|
163
|
+
pass
|
|
164
|
+
raise CheckharborError(code, message, resp.status_code)
|
|
165
|
+
return resp.json() # type: ignore[return-value]
|
|
166
|
+
|
|
167
|
+
def ping(self) -> dict[str, Any]:
|
|
168
|
+
"""Health check — returns ok + credits_remaining."""
|
|
169
|
+
return self._request("GET", "/v1/ping")
|
|
170
|
+
|
|
171
|
+
def validate_email(self, email: str) -> dict[str, Any]:
|
|
172
|
+
"""Validate a single email address (1 credit)."""
|
|
173
|
+
return self._request("POST", "/v1/email/validate", json={"email": email})
|
|
174
|
+
|
|
175
|
+
def validate_phone(
|
|
176
|
+
self,
|
|
177
|
+
phone: str,
|
|
178
|
+
*,
|
|
179
|
+
country_hint: Optional[str] = None,
|
|
180
|
+
hlr: Optional[bool] = None,
|
|
181
|
+
) -> dict[str, Any]:
|
|
182
|
+
"""Validate a phone number (1 credit offline, 5 with HLR)."""
|
|
183
|
+
payload: dict[str, Any] = {"phone": phone}
|
|
184
|
+
if country_hint is not None:
|
|
185
|
+
payload["country_hint"] = country_hint
|
|
186
|
+
if hlr is not None:
|
|
187
|
+
payload["hlr"] = hlr
|
|
188
|
+
return self._request("POST", "/v1/phone/validate", json=payload)
|
|
189
|
+
|
|
190
|
+
def ip_intel(self, ip: str) -> dict[str, Any]:
|
|
191
|
+
"""IP intelligence lookup (1 credit)."""
|
|
192
|
+
return self._request("POST", "/v1/ip/intel", json={"ip": ip})
|
|
193
|
+
|
|
194
|
+
def verify(
|
|
195
|
+
self,
|
|
196
|
+
*,
|
|
197
|
+
email: Optional[str] = None,
|
|
198
|
+
phone: Optional[str] = None,
|
|
199
|
+
ip: Optional[str] = None,
|
|
200
|
+
) -> dict[str, Any]:
|
|
201
|
+
"""
|
|
202
|
+
Unified fraud scoring (3 credits).
|
|
203
|
+
At least one of email, phone, ip is required.
|
|
204
|
+
"""
|
|
205
|
+
if not any([email, phone, ip]):
|
|
206
|
+
raise ValueError("verify() requires at least one of: email, phone, ip")
|
|
207
|
+
payload: dict[str, Any] = {}
|
|
208
|
+
if email:
|
|
209
|
+
payload["email"] = email
|
|
210
|
+
if phone:
|
|
211
|
+
payload["phone"] = phone
|
|
212
|
+
if ip:
|
|
213
|
+
payload["ip"] = ip
|
|
214
|
+
return self._request("POST", "/v1/verify", json=payload)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: checkharbor
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Checkharbor Python SDK — email/phone/IP validation
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://checkharbor.com
|
|
7
|
+
Project-URL: Documentation, https://docs.checkharbor.com
|
|
8
|
+
Project-URL: Repository, https://github.com/checkharbor/checkharbor-python
|
|
9
|
+
Keywords: checkharbor,email-validation,phone-validation,ip-intelligence
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: requests>=2.28
|
|
13
|
+
|
|
14
|
+
# checkharbor
|
|
15
|
+
|
|
16
|
+
Official Checkharbor Python SDK.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install checkharbor
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick start
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from checkharbor import Checkharbor
|
|
28
|
+
|
|
29
|
+
client = Checkharbor(api_key="chk_live_...")
|
|
30
|
+
|
|
31
|
+
# Validate email
|
|
32
|
+
email = client.validate_email("john@example.com")
|
|
33
|
+
print(email["score"], email["deliverability"])
|
|
34
|
+
|
|
35
|
+
# Validate phone
|
|
36
|
+
phone = client.validate_phone("+905321234567", country_hint="TR")
|
|
37
|
+
|
|
38
|
+
# IP intelligence
|
|
39
|
+
ip = client.ip_intel("84.17.45.10")
|
|
40
|
+
|
|
41
|
+
# Unified fraud check
|
|
42
|
+
fraud = client.verify(email="john@example.com", ip="84.17.45.10")
|
|
43
|
+
print(fraud["fraud_score"], fraud["risk"])
|
|
44
|
+
|
|
45
|
+
# Batch
|
|
46
|
+
job = client.batch.create(
|
|
47
|
+
type="email",
|
|
48
|
+
rows=["a@example.com", "b@example.com"],
|
|
49
|
+
webhook_url="https://myapp.com/webhook",
|
|
50
|
+
)
|
|
51
|
+
done = client.batch.wait_until_done(job["id"])
|
|
52
|
+
csv = client.batch.download_result(done)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Error handling
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from checkharbor import CheckharborError
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
result = client.validate_email("bad@test.com")
|
|
62
|
+
except CheckharborError as e:
|
|
63
|
+
print(e.code, e.status, str(e))
|
|
64
|
+
# e.g. "insufficient_credits" 402 "Not enough credits"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Development
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
71
|
+
pip install -e ".[dev]"
|
|
72
|
+
pytest
|
|
73
|
+
```
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
checkharbor/__init__.py,sha256=065UeTy8x4k_0gAqMEfVTUi1QKSro6Qv_-5rPdi5Io4,7017
|
|
2
|
+
checkharbor-0.1.0.dist-info/METADATA,sha256=jxKXPTC9OJZN5X6Ueupfe2w9zQwfPn9v5lAOzSMjp04,1682
|
|
3
|
+
checkharbor-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
4
|
+
checkharbor-0.1.0.dist-info/top_level.txt,sha256=ow8bI7EGXjUPmDOmOaSE2zy0FIDOaCxK2A-I_gBoBHs,12
|
|
5
|
+
checkharbor-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
checkharbor
|