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.
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ checkharbor