ipwhois-python 1.0.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.
- ipwhois_python-1.0.0/.gitignore +16 -0
- ipwhois_python-1.0.0/CHANGELOG.md +12 -0
- ipwhois_python-1.0.0/LICENSE +21 -0
- ipwhois_python-1.0.0/PKG-INFO +370 -0
- ipwhois_python-1.0.0/README.md +317 -0
- ipwhois_python-1.0.0/examples/basic.py +54 -0
- ipwhois_python-1.0.0/examples/bulk.py +63 -0
- ipwhois_python-1.0.0/examples/defaults.py +41 -0
- ipwhois_python-1.0.0/pyproject.toml +72 -0
- ipwhois_python-1.0.0/src/ipwhois/__init__.py +11 -0
- ipwhois_python-1.0.0/src/ipwhois/client.py +518 -0
- ipwhois_python-1.0.0/src/ipwhois/py.typed +0 -0
- ipwhois_python-1.0.0/tests/test_client.py +329 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `ipwhois-python` will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - 2026-05-08
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Initial release.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ipwhois.io
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ipwhois-python
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Official Python client for the ipwhois.io IP Geolocation API. Simple, dependency-free, supports single and bulk IP lookups.
|
|
5
|
+
Project-URL: Homepage, https://ipwhois.io
|
|
6
|
+
Project-URL: Documentation, https://ipwhois.io/documentation
|
|
7
|
+
Project-URL: Source, https://github.com/IPWhois/ipwhois-python
|
|
8
|
+
Project-URL: Issues, https://github.com/IPWhois/ipwhois-python/issues
|
|
9
|
+
Author: ipwhois.io
|
|
10
|
+
License: MIT License
|
|
11
|
+
|
|
12
|
+
Copyright (c) 2026 ipwhois.io
|
|
13
|
+
|
|
14
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
15
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
16
|
+
in the Software without restriction, including without limitation the rights
|
|
17
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
18
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
19
|
+
furnished to do so, subject to the following conditions:
|
|
20
|
+
|
|
21
|
+
The above copyright notice and this permission notice shall be included in all
|
|
22
|
+
copies or substantial portions of the Software.
|
|
23
|
+
|
|
24
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
25
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
26
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
27
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
28
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
29
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
30
|
+
SOFTWARE.
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Keywords: geoip,geolocation,ip-api,ip-geolocation,ip-locator,ip-lookup,ipwhois
|
|
33
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Operating System :: OS Independent
|
|
37
|
+
Classifier: Programming Language :: Python
|
|
38
|
+
Classifier: Programming Language :: Python :: 3
|
|
39
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
43
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
44
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
45
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
46
|
+
Classifier: Topic :: Internet
|
|
47
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
48
|
+
Classifier: Typing :: Typed
|
|
49
|
+
Requires-Python: >=3.8
|
|
50
|
+
Provides-Extra: dev
|
|
51
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
52
|
+
Description-Content-Type: text/markdown
|
|
53
|
+
|
|
54
|
+
# ipwhois-python
|
|
55
|
+
|
|
56
|
+
[](https://pypi.org/project/ipwhois-python/)
|
|
57
|
+
[](https://pypi.org/project/ipwhois-python/)
|
|
58
|
+
[](LICENSE)
|
|
59
|
+
|
|
60
|
+
Official, dependency-free Python client for the [ipwhois.io](https://ipwhois.io) IP Geolocation API.
|
|
61
|
+
|
|
62
|
+
- ✅ Single and bulk IP lookups (IPv4 and IPv6)
|
|
63
|
+
- ✅ Works with both the **Free** and **Paid** plans
|
|
64
|
+
- ✅ HTTPS by default
|
|
65
|
+
- ✅ Localisation, field selection, threat detection, rate info
|
|
66
|
+
- ✅ Never raises — all errors returned as `success: False` dicts
|
|
67
|
+
- ✅ No external dependencies — only the Python standard library
|
|
68
|
+
- ✅ Python 3.8+
|
|
69
|
+
|
|
70
|
+
## Installation
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install ipwhois-python
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Free vs Paid plan
|
|
77
|
+
|
|
78
|
+
The same `Client` class is used for both plans. The only difference is whether
|
|
79
|
+
you pass an API key:
|
|
80
|
+
|
|
81
|
+
- **Free plan** — create the client **without arguments**. No API key, no
|
|
82
|
+
signup required. Suitable for low-traffic and non-commercial use.
|
|
83
|
+
- **Paid plan** — create the client **with your API key** from
|
|
84
|
+
<https://ipwhois.io>. Higher limits, plus access to bulk lookups and
|
|
85
|
+
threat-detection data.
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from ipwhois import Client
|
|
89
|
+
|
|
90
|
+
free = Client() # Free plan — no API key
|
|
91
|
+
paid = Client("YOUR_API_KEY") # Paid plan — with API key
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Everything else (`lookup()`, options, error handling) is identical.
|
|
95
|
+
|
|
96
|
+
## Quick start — Free plan (no API key)
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from ipwhois import Client
|
|
100
|
+
|
|
101
|
+
client = Client() # no API key
|
|
102
|
+
|
|
103
|
+
info = client.lookup("8.8.8.8")
|
|
104
|
+
|
|
105
|
+
print(info["country"], info["flag"]["emoji"])
|
|
106
|
+
# → United States 🇺🇸
|
|
107
|
+
|
|
108
|
+
print(f"{info['city']}, {info['region']}")
|
|
109
|
+
# → Mountain View, California
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Quick start — Paid plan (with API key)
|
|
113
|
+
|
|
114
|
+
Get an API key at <https://ipwhois.io> and pass it to the constructor:
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from ipwhois import Client
|
|
118
|
+
|
|
119
|
+
client = Client("YOUR_API_KEY") # with API key
|
|
120
|
+
|
|
121
|
+
info = client.lookup("8.8.8.8")
|
|
122
|
+
|
|
123
|
+
print(info["country"], info["flag"]["emoji"])
|
|
124
|
+
# → United States 🇺🇸
|
|
125
|
+
|
|
126
|
+
print(f"{info['city']}, {info['region']}")
|
|
127
|
+
# → Mountain View, California
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
> ℹ️ Pass nothing to look up your own public IP: `client.lookup()` — works
|
|
131
|
+
> on both plans.
|
|
132
|
+
|
|
133
|
+
## Lookup options
|
|
134
|
+
|
|
135
|
+
Every option below can be passed per call as a keyword argument, or set once
|
|
136
|
+
on the client as a default.
|
|
137
|
+
|
|
138
|
+
| Option | Type | Plans needed | Description |
|
|
139
|
+
| ------------ | ------- | -------------------- | ---------------------------------------------------------------------- |
|
|
140
|
+
| `lang` | str | Free + Paid | One of: `en`, `ru`, `de`, `es`, `pt-BR`, `fr`, `zh-CN`, `ja` |
|
|
141
|
+
| `fields` | list | Free + Paid | Restrict the response to specific fields (e.g. `["country", "city"]`) |
|
|
142
|
+
| `output` | str | Free + Paid | `json` (default), `xml`, `csv` |
|
|
143
|
+
| `rate` | bool | Basic and above | Include the `rate` block (`limit`, `remaining`) |
|
|
144
|
+
| `security` | bool | Business and above | Include the `security` block (proxy/vpn/tor/hosting) |
|
|
145
|
+
|
|
146
|
+
### Setting defaults once
|
|
147
|
+
|
|
148
|
+
If you make many calls with the same options, set them once and forget:
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
# Free plan
|
|
152
|
+
client = (
|
|
153
|
+
Client()
|
|
154
|
+
.set_language("en")
|
|
155
|
+
.set_fields(["country", "city", "flag.emoji"])
|
|
156
|
+
.set_timeout(8)
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
client.lookup("8.8.8.8") # uses all of the above
|
|
160
|
+
client.lookup("1.1.1.1", lang="de") # per-call options override defaults
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
# Paid plan
|
|
165
|
+
client = (
|
|
166
|
+
Client("YOUR_API_KEY")
|
|
167
|
+
.set_language("en")
|
|
168
|
+
.set_fields(["country", "city", "flag.emoji"])
|
|
169
|
+
.set_timeout(8)
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
client.lookup("8.8.8.8") # uses all of the above
|
|
173
|
+
client.lookup("1.1.1.1", lang="de") # per-call options override defaults
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
> ℹ️ Paid plans additionally support `set_security(True)` (Business+) and
|
|
177
|
+
> `set_rate(True)` (Basic+). See the table above for what's available where.
|
|
178
|
+
|
|
179
|
+
## HTTPS encryption
|
|
180
|
+
|
|
181
|
+
By default, all requests are sent over HTTPS. If you need to disable it (for
|
|
182
|
+
example, in environments without an up-to-date CA bundle), pass `ssl=False`
|
|
183
|
+
to the constructor:
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
from ipwhois import Client
|
|
187
|
+
|
|
188
|
+
# Free plan
|
|
189
|
+
client = Client(ssl=False)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
from ipwhois import Client
|
|
194
|
+
|
|
195
|
+
# Paid plan
|
|
196
|
+
client = Client("YOUR_API_KEY", ssl=False)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
> ℹ️ HTTPS is strongly recommended for production traffic — your API key is
|
|
200
|
+
> sent in the query string and would otherwise travel in clear text.
|
|
201
|
+
|
|
202
|
+
## Bulk lookup (Paid plan only)
|
|
203
|
+
|
|
204
|
+
The bulk endpoint sends **up to 100 IPs** in a single GET request. Each
|
|
205
|
+
address counts as one credit. Available on the **Business** and **Unlimited**
|
|
206
|
+
plans.
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
from ipwhois import Client
|
|
210
|
+
|
|
211
|
+
client = Client("YOUR_API_KEY")
|
|
212
|
+
|
|
213
|
+
results = client.bulk_lookup([
|
|
214
|
+
"8.8.8.8",
|
|
215
|
+
"1.1.1.1",
|
|
216
|
+
"208.67.222.222",
|
|
217
|
+
"2c0f:fb50:4003::", # IPv6 is fine — mix freely
|
|
218
|
+
])
|
|
219
|
+
|
|
220
|
+
for row in results:
|
|
221
|
+
if row.get("success") is False:
|
|
222
|
+
# Per-IP errors (e.g. "Invalid IP address") are returned inline,
|
|
223
|
+
# they do NOT raise — the rest of the batch is still usable.
|
|
224
|
+
print(f"skip {row['ip']}: {row['message']}")
|
|
225
|
+
continue
|
|
226
|
+
print(f"{row['ip']} → {row['country']}")
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
> ℹ️ Bulk requires an API key. Calling `bulk_lookup()` without one will fail
|
|
230
|
+
> at the API level.
|
|
231
|
+
|
|
232
|
+
## Error handling
|
|
233
|
+
|
|
234
|
+
**The library never raises.** Every failure — invalid IP, bad API key, rate
|
|
235
|
+
limit, network outage, bad options — comes back inside the response dict
|
|
236
|
+
with `success` set to `False` and a `message`. Just check
|
|
237
|
+
`info["success"]` after every call:
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
info = client.lookup("8.8.8.8")
|
|
241
|
+
|
|
242
|
+
if not info["success"]:
|
|
243
|
+
print(f"Lookup failed: {info['message']}")
|
|
244
|
+
return
|
|
245
|
+
|
|
246
|
+
print(info["country"])
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
This means an outage of the ipwhois.io API (or of your machine's DNS,
|
|
250
|
+
connection, etc.) will never surface as an unhandled exception in your
|
|
251
|
+
application — you decide how to react.
|
|
252
|
+
|
|
253
|
+
### Error response fields
|
|
254
|
+
|
|
255
|
+
Every error response contains `success: False` and a `message`. Some errors
|
|
256
|
+
include extra fields you can branch on:
|
|
257
|
+
|
|
258
|
+
| Field | When it's present |
|
|
259
|
+
| -------------- | ---------------------------------------------------------------------------- |
|
|
260
|
+
| `error_type` | `'network'` or `'invalid_argument'` — for non-API errors |
|
|
261
|
+
| `http_status` | On HTTP 4xx / 5xx responses |
|
|
262
|
+
| `retry_after` | On HTTP 429 if the API sent a `Retry-After` header |
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
import time
|
|
266
|
+
|
|
267
|
+
info = client.lookup("8.8.8.8")
|
|
268
|
+
|
|
269
|
+
if not info["success"]:
|
|
270
|
+
if info.get("http_status") == 429:
|
|
271
|
+
time.sleep(info.get("retry_after", 60))
|
|
272
|
+
# ...retry
|
|
273
|
+
|
|
274
|
+
if info.get("error_type") == "network":
|
|
275
|
+
# DNS failure, connection refused, timeout, ...
|
|
276
|
+
pass
|
|
277
|
+
|
|
278
|
+
print(f"Error: {info['message']}")
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Response shape
|
|
282
|
+
|
|
283
|
+
A successful response includes (depending on your plan and selected options):
|
|
284
|
+
|
|
285
|
+
```jsonc
|
|
286
|
+
{
|
|
287
|
+
"ip": "8.8.4.4",
|
|
288
|
+
"success": true,
|
|
289
|
+
"type": "IPv4",
|
|
290
|
+
"continent": "North America",
|
|
291
|
+
"continent_code": "NA",
|
|
292
|
+
"country": "United States",
|
|
293
|
+
"country_code": "US",
|
|
294
|
+
"region": "California",
|
|
295
|
+
"region_code": "CA",
|
|
296
|
+
"city": "Mountain View",
|
|
297
|
+
"latitude": 37.3860517,
|
|
298
|
+
"longitude": -122.0838511,
|
|
299
|
+
"is_eu": false,
|
|
300
|
+
"postal": "94039",
|
|
301
|
+
"calling_code": "1",
|
|
302
|
+
"capital": "Washington D.C.",
|
|
303
|
+
"borders": "CA,MX",
|
|
304
|
+
"flag": {
|
|
305
|
+
"img": "https://cdn.ipwhois.io/flags/us.svg",
|
|
306
|
+
"emoji": "🇺🇸",
|
|
307
|
+
"emoji_unicode": "U+1F1FA U+1F1F8"
|
|
308
|
+
},
|
|
309
|
+
"connection": {
|
|
310
|
+
"asn": 15169,
|
|
311
|
+
"org": "Google LLC",
|
|
312
|
+
"isp": "Google LLC",
|
|
313
|
+
"domain": "google.com"
|
|
314
|
+
},
|
|
315
|
+
"timezone": {
|
|
316
|
+
"id": "America/Los_Angeles",
|
|
317
|
+
"abbr": "PDT",
|
|
318
|
+
"is_dst": true,
|
|
319
|
+
"offset": -25200,
|
|
320
|
+
"utc": "-07:00",
|
|
321
|
+
"current_time": "2026-05-08T14:31:48-07:00"
|
|
322
|
+
},
|
|
323
|
+
"currency": {
|
|
324
|
+
"name": "US Dollar",
|
|
325
|
+
"code": "USD",
|
|
326
|
+
"symbol": "$",
|
|
327
|
+
"plural": "US dollars",
|
|
328
|
+
"exchange_rate": 1
|
|
329
|
+
},
|
|
330
|
+
"security": {
|
|
331
|
+
"anonymous": false,
|
|
332
|
+
"proxy": false,
|
|
333
|
+
"vpn": false,
|
|
334
|
+
"tor": false,
|
|
335
|
+
"hosting": false
|
|
336
|
+
},
|
|
337
|
+
"rate": {
|
|
338
|
+
"limit": 250000,
|
|
339
|
+
"remaining": 50155
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
For the full field reference, see the [official documentation](https://ipwhois.io/documentation).
|
|
345
|
+
|
|
346
|
+
An **error** response looks like:
|
|
347
|
+
|
|
348
|
+
```jsonc
|
|
349
|
+
{
|
|
350
|
+
"success": false,
|
|
351
|
+
"message": "Invalid IP address",
|
|
352
|
+
"http_status": 400 // present for HTTP 4xx / 5xx
|
|
353
|
+
// "retry_after": 60 // additionally present on HTTP 429 if the API sent a Retry-After header
|
|
354
|
+
// "error_type": "network" // present for non-API errors: 'network', 'invalid_argument'
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## Requirements
|
|
359
|
+
|
|
360
|
+
- Python **3.8** or newer
|
|
361
|
+
- No third-party dependencies — only the standard library (`urllib`, `json`)
|
|
362
|
+
|
|
363
|
+
## Contributing
|
|
364
|
+
|
|
365
|
+
Issues and pull requests are welcome on
|
|
366
|
+
[GitHub](https://github.com/IPWhois/ipwhois-python).
|
|
367
|
+
|
|
368
|
+
## License
|
|
369
|
+
|
|
370
|
+
[MIT](LICENSE) © ipwhois.io
|