ipwhois-python 1.0.1__tar.gz → 1.0.2__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.2/CHANGELOG.md +85 -0
- {ipwhois_python-1.0.1 → ipwhois_python-1.0.2}/PKG-INFO +19 -21
- {ipwhois_python-1.0.1 → ipwhois_python-1.0.2}/README.md +18 -20
- {ipwhois_python-1.0.1 → ipwhois_python-1.0.2}/examples/basic.py +1 -1
- {ipwhois_python-1.0.1 → ipwhois_python-1.0.2}/examples/defaults.py +1 -1
- {ipwhois_python-1.0.1 → ipwhois_python-1.0.2}/pyproject.toml +1 -1
- {ipwhois_python-1.0.1 → ipwhois_python-1.0.2}/src/ipwhois/ipwhois.py +18 -35
- {ipwhois_python-1.0.1 → ipwhois_python-1.0.2}/tests/test_ipwhois.py +22 -21
- ipwhois_python-1.0.1/CHANGELOG.md +0 -41
- {ipwhois_python-1.0.1 → ipwhois_python-1.0.2}/.gitignore +0 -0
- {ipwhois_python-1.0.1 → ipwhois_python-1.0.2}/LICENSE +0 -0
- {ipwhois_python-1.0.1 → ipwhois_python-1.0.2}/examples/bulk.py +0 -0
- {ipwhois_python-1.0.1 → ipwhois_python-1.0.2}/src/ipwhois/__init__.py +0 -0
- {ipwhois_python-1.0.1 → ipwhois_python-1.0.2}/src/ipwhois/py.typed +0 -0
|
@@ -0,0 +1,85 @@
|
|
|
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.2] - 2026-05-10
|
|
9
|
+
|
|
10
|
+
### Removed
|
|
11
|
+
|
|
12
|
+
- **The `output` option has been removed.** The library only ever processed
|
|
13
|
+
JSON responses meaningfully, so `output="xml"` and `output="csv"` were a
|
|
14
|
+
thin pass-through that returned the raw payload as a string. The option
|
|
15
|
+
has been dropped from `lookup()`, `bulk_lookup()`, and the constructor's
|
|
16
|
+
keyword arguments; the `IPWhois.SUPPORTED_OUTPUTS` constant is gone.
|
|
17
|
+
Passing `output=...` will silently no-op.
|
|
18
|
+
- The 2xx + non-JSON `{"success": True, "raw": ...}` fallback in the
|
|
19
|
+
response handler (which only existed to support the removed `output`
|
|
20
|
+
parameter) is gone. The API always returns JSON, so any non-JSON 2xx
|
|
21
|
+
body is now treated as a transport error and returned as a
|
|
22
|
+
`success: False` dict.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- `set_fields()` docstring now mentions that `"success"` should be included
|
|
27
|
+
in the field whitelist if you rely on `info["success"]` for error
|
|
28
|
+
checking — when `fields` is set, the API only returns the fields you list.
|
|
29
|
+
- README "Setting defaults once" section rewritten for clarity: the two
|
|
30
|
+
ways of passing options (per call vs. as defaults), the available
|
|
31
|
+
setters, and the `success`-in-`fields` gotcha are now spelled out
|
|
32
|
+
explicitly. The free/paid example pair was collapsed into a single
|
|
33
|
+
example, since the setters work identically on both plans.
|
|
34
|
+
- All examples that filter fields (`README.md`, `examples/basic.py`,
|
|
35
|
+
`examples/defaults.py`) now include `"success"` in the field list.
|
|
36
|
+
|
|
37
|
+
### Migration
|
|
38
|
+
|
|
39
|
+
If your code passes `output="json"` you can simply remove it — the library
|
|
40
|
+
always returns the decoded JSON anyway. If you were relying on
|
|
41
|
+
`output="xml"` or `output="csv"` to get the raw payload, that use case is
|
|
42
|
+
no longer supported; call the API directly with `urllib` for those formats.
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
# Before (1.0.1):
|
|
46
|
+
info = ipwhois.lookup("8.8.8.8", output="json", fields=["country", "city"])
|
|
47
|
+
|
|
48
|
+
# After (1.0.2):
|
|
49
|
+
info = ipwhois.lookup("8.8.8.8", fields=["success", "country", "city"])
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## [1.0.1] - 2026-05-09
|
|
53
|
+
|
|
54
|
+
### Changed
|
|
55
|
+
|
|
56
|
+
- **Renamed the main class `Client` to `IPWhois`** for consistency with the
|
|
57
|
+
package and brand. The recommended import is now
|
|
58
|
+
`from ipwhois import IPWhois`. The source module moved from
|
|
59
|
+
`src/ipwhois/client.py` to `src/ipwhois/ipwhois.py`, and the test module
|
|
60
|
+
from `tests/test_client.py` to `tests/test_ipwhois.py`. Public behaviour,
|
|
61
|
+
method signatures, constructor arguments, and return shapes are all
|
|
62
|
+
unchanged.
|
|
63
|
+
|
|
64
|
+
### Migration
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
# Before (1.0.0):
|
|
68
|
+
from ipwhois import Client
|
|
69
|
+
client = Client("YOUR_API_KEY")
|
|
70
|
+
info = client.lookup("8.8.8.8")
|
|
71
|
+
|
|
72
|
+
# After (1.0.1+):
|
|
73
|
+
from ipwhois import IPWhois
|
|
74
|
+
ipwhois = IPWhois("YOUR_API_KEY")
|
|
75
|
+
info = ipwhois.lookup("8.8.8.8")
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The variable name (`client`, `ipwhois`, anything else) is up to you; only
|
|
79
|
+
the class identifier changed.
|
|
80
|
+
|
|
81
|
+
## [1.0.0] - 2026-05-08
|
|
82
|
+
|
|
83
|
+
### Added
|
|
84
|
+
|
|
85
|
+
- Initial release.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ipwhois-python
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: Official Python client for the ipwhois.io IP Geolocation API. Simple, dependency-free, supports single and bulk IP lookups.
|
|
5
5
|
Project-URL: Homepage, https://ipwhois.io
|
|
6
6
|
Project-URL: Documentation, https://ipwhois.io/documentation
|
|
@@ -139,42 +139,40 @@ on the client as a default.
|
|
|
139
139
|
| ------------ | ------- | -------------------- | ---------------------------------------------------------------------- |
|
|
140
140
|
| `lang` | str | Free + Paid | One of: `en`, `ru`, `de`, `es`, `pt-BR`, `fr`, `zh-CN`, `ja` |
|
|
141
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
142
|
| `rate` | bool | Basic and above | Include the `rate` block (`limit`, `remaining`) |
|
|
144
143
|
| `security` | bool | Business and above | Include the `security` block (proxy/vpn/tor/hosting) |
|
|
145
144
|
|
|
146
145
|
### Setting defaults once
|
|
147
146
|
|
|
148
|
-
|
|
147
|
+
Every option can be passed two ways: **per call** (as a keyword argument to
|
|
148
|
+
`lookup()` / `bulk_lookup()`) or **once as a default** on the client. Per-call
|
|
149
|
+
options always override the defaults, so it's safe to set sensible defaults
|
|
150
|
+
and only override what differs for a specific call.
|
|
151
|
+
|
|
152
|
+
Defaults are set with fluent setters — `set_language()`, `set_fields()`,
|
|
153
|
+
`set_security()`, `set_rate()`, `set_timeout()`, `set_connect_timeout()`,
|
|
154
|
+
`set_user_agent()` — and can be chained:
|
|
149
155
|
|
|
150
156
|
```python
|
|
151
|
-
#
|
|
157
|
+
# Pass "YOUR_API_KEY" to the constructor for the paid plan; otherwise omit it.
|
|
152
158
|
ipwhois = (
|
|
153
159
|
IPWhois()
|
|
154
160
|
.set_language("en")
|
|
155
|
-
.set_fields(["country", "city", "flag.emoji"])
|
|
161
|
+
.set_fields(["success", "country", "city", "flag.emoji"])
|
|
156
162
|
.set_timeout(8)
|
|
157
163
|
)
|
|
158
164
|
|
|
159
|
-
ipwhois.lookup("8.8.8.8")
|
|
160
|
-
ipwhois.lookup("1.1.1.1", lang="de")
|
|
165
|
+
ipwhois.lookup("8.8.8.8") # uses lang=en, the field whitelist, and timeout=8
|
|
166
|
+
ipwhois.lookup("1.1.1.1", lang="de") # overrides lang for this single call only
|
|
161
167
|
```
|
|
162
168
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
.set_language("en")
|
|
168
|
-
.set_fields(["country", "city", "flag.emoji"])
|
|
169
|
-
.set_timeout(8)
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
ipwhois.lookup("8.8.8.8") # uses all of the above
|
|
173
|
-
ipwhois.lookup("1.1.1.1", lang="de") # per-call options override defaults
|
|
174
|
-
```
|
|
169
|
+
> ⚠️ When you restrict fields with `set_fields()` (or the per-call `fields=`
|
|
170
|
+
> keyword), the API only returns the fields you ask for. Always include
|
|
171
|
+
> `"success"` in the list if you rely on `info["success"]` for error
|
|
172
|
+
> checking — otherwise the field will be missing on responses.
|
|
175
173
|
|
|
176
|
-
> ℹ️
|
|
177
|
-
>
|
|
174
|
+
> ℹ️ `set_security(True)` requires Business+ and `set_rate(True)` requires
|
|
175
|
+
> Basic+. See the table above for what's available where.
|
|
178
176
|
|
|
179
177
|
## HTTPS encryption
|
|
180
178
|
|
|
@@ -86,42 +86,40 @@ on the client as a default.
|
|
|
86
86
|
| ------------ | ------- | -------------------- | ---------------------------------------------------------------------- |
|
|
87
87
|
| `lang` | str | Free + Paid | One of: `en`, `ru`, `de`, `es`, `pt-BR`, `fr`, `zh-CN`, `ja` |
|
|
88
88
|
| `fields` | list | Free + Paid | Restrict the response to specific fields (e.g. `["country", "city"]`) |
|
|
89
|
-
| `output` | str | Free + Paid | `json` (default), `xml`, `csv` |
|
|
90
89
|
| `rate` | bool | Basic and above | Include the `rate` block (`limit`, `remaining`) |
|
|
91
90
|
| `security` | bool | Business and above | Include the `security` block (proxy/vpn/tor/hosting) |
|
|
92
91
|
|
|
93
92
|
### Setting defaults once
|
|
94
93
|
|
|
95
|
-
|
|
94
|
+
Every option can be passed two ways: **per call** (as a keyword argument to
|
|
95
|
+
`lookup()` / `bulk_lookup()`) or **once as a default** on the client. Per-call
|
|
96
|
+
options always override the defaults, so it's safe to set sensible defaults
|
|
97
|
+
and only override what differs for a specific call.
|
|
98
|
+
|
|
99
|
+
Defaults are set with fluent setters — `set_language()`, `set_fields()`,
|
|
100
|
+
`set_security()`, `set_rate()`, `set_timeout()`, `set_connect_timeout()`,
|
|
101
|
+
`set_user_agent()` — and can be chained:
|
|
96
102
|
|
|
97
103
|
```python
|
|
98
|
-
#
|
|
104
|
+
# Pass "YOUR_API_KEY" to the constructor for the paid plan; otherwise omit it.
|
|
99
105
|
ipwhois = (
|
|
100
106
|
IPWhois()
|
|
101
107
|
.set_language("en")
|
|
102
|
-
.set_fields(["country", "city", "flag.emoji"])
|
|
108
|
+
.set_fields(["success", "country", "city", "flag.emoji"])
|
|
103
109
|
.set_timeout(8)
|
|
104
110
|
)
|
|
105
111
|
|
|
106
|
-
ipwhois.lookup("8.8.8.8")
|
|
107
|
-
ipwhois.lookup("1.1.1.1", lang="de")
|
|
112
|
+
ipwhois.lookup("8.8.8.8") # uses lang=en, the field whitelist, and timeout=8
|
|
113
|
+
ipwhois.lookup("1.1.1.1", lang="de") # overrides lang for this single call only
|
|
108
114
|
```
|
|
109
115
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
.set_language("en")
|
|
115
|
-
.set_fields(["country", "city", "flag.emoji"])
|
|
116
|
-
.set_timeout(8)
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
ipwhois.lookup("8.8.8.8") # uses all of the above
|
|
120
|
-
ipwhois.lookup("1.1.1.1", lang="de") # per-call options override defaults
|
|
121
|
-
```
|
|
116
|
+
> ⚠️ When you restrict fields with `set_fields()` (or the per-call `fields=`
|
|
117
|
+
> keyword), the API only returns the fields you ask for. Always include
|
|
118
|
+
> `"success"` in the list if you rely on `info["success"]` for error
|
|
119
|
+
> checking — otherwise the field will be missing on responses.
|
|
122
120
|
|
|
123
|
-
> ℹ️
|
|
124
|
-
>
|
|
121
|
+
> ℹ️ `set_security(True)` requires Business+ and `set_rate(True)` requires
|
|
122
|
+
> Basic+. See the table above for what's available where.
|
|
125
123
|
|
|
126
124
|
## HTTPS encryption
|
|
127
125
|
|
|
@@ -46,7 +46,7 @@ paid = IPWhois("YOUR_API_KEY")
|
|
|
46
46
|
info = paid.lookup(
|
|
47
47
|
"1.1.1.1",
|
|
48
48
|
lang="en", # localised country/city/...
|
|
49
|
-
fields=["country", "city", "connection.isp", "flag.emoji"],
|
|
49
|
+
fields=["success", "country", "city", "connection.isp", "flag.emoji"],
|
|
50
50
|
security=True, # include proxy/vpn/tor flags
|
|
51
51
|
rate=True, # include rate-limit info
|
|
52
52
|
)
|
|
@@ -14,7 +14,7 @@ from ipwhois import IPWhois
|
|
|
14
14
|
ipwhois = (
|
|
15
15
|
IPWhois("YOUR_API_KEY")
|
|
16
16
|
.set_language("en")
|
|
17
|
-
.set_fields(["country", "city", "flag.emoji", "connection.isp"])
|
|
17
|
+
.set_fields(["success", "country", "city", "flag.emoji", "connection.isp"])
|
|
18
18
|
.set_security(True)
|
|
19
19
|
.set_timeout(8)
|
|
20
20
|
)
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ipwhois-python"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.2"
|
|
8
8
|
description = "Official Python client for the ipwhois.io IP Geolocation API. Simple, dependency-free, supports single and bulk IP lookups."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -55,7 +55,7 @@ class IPWhois:
|
|
|
55
55
|
"""
|
|
56
56
|
|
|
57
57
|
#: Library version, used in the default User-Agent header.
|
|
58
|
-
VERSION: str = "1.0.
|
|
58
|
+
VERSION: str = "1.0.2"
|
|
59
59
|
|
|
60
60
|
#: Free-plan endpoint host (used when no API key is provided).
|
|
61
61
|
HOST_FREE: str = "ipwho.is"
|
|
@@ -78,16 +78,13 @@ class IPWhois:
|
|
|
78
78
|
"ja",
|
|
79
79
|
)
|
|
80
80
|
|
|
81
|
-
#: Output formats supported by the ``output`` parameter.
|
|
82
|
-
SUPPORTED_OUTPUTS: tuple = ("json", "xml", "csv")
|
|
83
|
-
|
|
84
81
|
def __init__(self, api_key: Optional[str] = None, **options: Any) -> None:
|
|
85
82
|
"""Create a new client.
|
|
86
83
|
|
|
87
84
|
:param api_key: Your ipwhois.io API key. Omit for the free plan.
|
|
88
85
|
:param options: Optional defaults applied to every request. Recognised
|
|
89
|
-
keys: ``lang``, ``fields``, ``security``, ``rate``, ``
|
|
90
|
-
``
|
|
86
|
+
keys: ``lang``, ``fields``, ``security``, ``rate``, ``ssl``,
|
|
87
|
+
``timeout``, ``connect_timeout``, ``user_agent``.
|
|
91
88
|
"""
|
|
92
89
|
self._api_key: Optional[str] = api_key
|
|
93
90
|
self._user_agent: str = str(
|
|
@@ -117,7 +114,7 @@ class IPWhois:
|
|
|
117
114
|
|
|
118
115
|
:param ip: IPv4 or IPv6 address. ``None`` (default) = current IP.
|
|
119
116
|
:param options: Per-call options: ``lang``, ``fields``,
|
|
120
|
-
``security`` (bool), ``rate`` (bool)
|
|
117
|
+
``security`` (bool), ``rate`` (bool).
|
|
121
118
|
:returns: Decoded JSON response. On any error (API, network, bad
|
|
122
119
|
input) the dict contains ``success`` set to ``False`` and a
|
|
123
120
|
``message``. The library never raises.
|
|
@@ -232,10 +229,14 @@ class IPWhois:
|
|
|
232
229
|
) -> "IPWhois":
|
|
233
230
|
"""Restrict every response to a fixed set of fields by default.
|
|
234
231
|
|
|
232
|
+
Include ``"success"`` in the list if you rely on ``info["success"]``
|
|
233
|
+
for error checking -- when ``fields`` is set, the API only returns
|
|
234
|
+
the fields you ask for.
|
|
235
|
+
|
|
235
236
|
:param fields: An iterable of field names, e.g.
|
|
236
|
-
``["country", "city", "flag.emoji"]``. A pre-joined
|
|
237
|
-
string is also accepted and passed through
|
|
238
|
-
``None`` to clear any previously-set default.
|
|
237
|
+
``["success", "country", "city", "flag.emoji"]``. A pre-joined
|
|
238
|
+
comma-separated string is also accepted and passed through
|
|
239
|
+
unchanged. Pass ``None`` to clear any previously-set default.
|
|
239
240
|
"""
|
|
240
241
|
# Strings are iterable in Python, so list("country,city") would
|
|
241
242
|
# explode into individual characters. Keep strings as strings.
|
|
@@ -317,17 +318,6 @@ class IPWhois:
|
|
|
317
318
|
"error_type": "invalid_argument",
|
|
318
319
|
}
|
|
319
320
|
|
|
320
|
-
output = merged.get("output")
|
|
321
|
-
if output is not None and output not in self.SUPPORTED_OUTPUTS:
|
|
322
|
-
return {
|
|
323
|
-
"success": False,
|
|
324
|
-
"message": (
|
|
325
|
-
f'Unsupported output format "{output}". Supported: '
|
|
326
|
-
f"{', '.join(self.SUPPORTED_OUTPUTS)}."
|
|
327
|
-
),
|
|
328
|
-
"error_type": "invalid_argument",
|
|
329
|
-
}
|
|
330
|
-
|
|
331
321
|
return None
|
|
332
322
|
|
|
333
323
|
def _build_url(self, path: str, options: Dict[str, Any]) -> str:
|
|
@@ -345,9 +335,6 @@ class IPWhois:
|
|
|
345
335
|
if "lang" in merged and merged["lang"] is not None:
|
|
346
336
|
query.append(("lang", str(merged["lang"])))
|
|
347
337
|
|
|
348
|
-
if "output" in merged and merged["output"] is not None:
|
|
349
|
-
query.append(("output", str(merged["output"])))
|
|
350
|
-
|
|
351
338
|
if "fields" in merged and merged["fields"] is not None:
|
|
352
339
|
fields = merged["fields"]
|
|
353
340
|
if isinstance(fields, (list, tuple)):
|
|
@@ -432,23 +419,19 @@ class IPWhois:
|
|
|
432
419
|
try:
|
|
433
420
|
decoded = json.loads(body)
|
|
434
421
|
except json.JSONDecodeError:
|
|
435
|
-
#
|
|
436
|
-
#
|
|
437
|
-
#
|
|
438
|
-
#
|
|
439
|
-
#
|
|
440
|
-
if 200 <= status < 300:
|
|
441
|
-
return {"success": True, "raw": body}
|
|
442
|
-
|
|
443
|
-
# Non-JSON 4xx/5xx -- synthesise an error dict so the caller
|
|
444
|
-
# can handle it the same way as a normal API error.
|
|
422
|
+
# The ipwhois API always returns JSON. A non-JSON body means
|
|
423
|
+
# something went wrong upstream (gateway error page, captive
|
|
424
|
+
# portal, hijacked response, ...) -- synthesise an error dict
|
|
425
|
+
# so the caller can handle it the same way as a normal API
|
|
426
|
+
# error.
|
|
445
427
|
snippet = " ".join(body.split())
|
|
446
428
|
if len(snippet) > 200:
|
|
447
429
|
snippet = snippet[:200] + "\u2026"
|
|
448
430
|
return {
|
|
449
431
|
"success": False,
|
|
450
432
|
"message": (
|
|
451
|
-
f"
|
|
433
|
+
f"Invalid JSON returned by ipwhois API "
|
|
434
|
+
f"(HTTP {status}): {snippet}"
|
|
452
435
|
),
|
|
453
436
|
"http_status": status,
|
|
454
437
|
}
|
|
@@ -91,14 +91,6 @@ def test_invalid_language_returns_error_dict() -> None:
|
|
|
91
91
|
assert "klingon" in result.get("message", "")
|
|
92
92
|
|
|
93
93
|
|
|
94
|
-
def test_invalid_output_returns_error_dict() -> None:
|
|
95
|
-
result = IPWhois().lookup("8.8.8.8", output="yaml")
|
|
96
|
-
|
|
97
|
-
assert result["success"] is False
|
|
98
|
-
assert result.get("error_type") == "invalid_argument"
|
|
99
|
-
assert "yaml" in result.get("message", "")
|
|
100
|
-
|
|
101
|
-
|
|
102
94
|
def test_bulk_lookup_refuses_empty_list() -> None:
|
|
103
95
|
result = IPWhois("K").bulk_lookup([])
|
|
104
96
|
|
|
@@ -290,16 +282,24 @@ def test_constructor_tolerates_bad_timeout() -> None:
|
|
|
290
282
|
assert ipwhois._connect_timeout == 5
|
|
291
283
|
|
|
292
284
|
|
|
293
|
-
def
|
|
294
|
-
#
|
|
295
|
-
#
|
|
296
|
-
#
|
|
297
|
-
ipwhois = IPWhois()
|
|
285
|
+
def test_output_option_is_silently_dropped() -> None:
|
|
286
|
+
# The `output` parameter was removed in 1.0.2. Passing it must NOT raise
|
|
287
|
+
# or trip validation -- it's just ignored, and the resulting URL must
|
|
288
|
+
# not contain an `output=...` query string.
|
|
289
|
+
ipwhois = IPWhois("K")
|
|
290
|
+
url = ipwhois._build_url("/8.8.8.8", {"output": "xml", "lang": "en"})
|
|
291
|
+
|
|
292
|
+
assert "output=" not in url
|
|
293
|
+
# Other options next to it still work.
|
|
294
|
+
assert "lang=en" in url
|
|
298
295
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
#
|
|
302
|
-
|
|
296
|
+
|
|
297
|
+
def test_non_json_response_is_treated_as_error() -> None:
|
|
298
|
+
# The API always returns JSON. A non-JSON 2xx body now indicates a
|
|
299
|
+
# transport problem (gateway error page, captive portal, ...) rather
|
|
300
|
+
# than legitimate XML/CSV output -- the `output` parameter was
|
|
301
|
+
# removed in 1.0.2. Expect a `success: False` error dict instead of
|
|
302
|
+
# the old `{"success": True, "raw": ...}` wrapper.
|
|
303
303
|
from unittest.mock import patch
|
|
304
304
|
|
|
305
305
|
class _FakeResp:
|
|
@@ -321,9 +321,10 @@ def test_raw_response_includes_success_true() -> None:
|
|
|
321
321
|
def getcode(self) -> int:
|
|
322
322
|
return 200
|
|
323
323
|
|
|
324
|
-
fake = _FakeResp(b"<
|
|
324
|
+
fake = _FakeResp(b"<html>captive portal</html>")
|
|
325
325
|
with patch("urllib.request.urlopen", return_value=fake):
|
|
326
|
-
result =
|
|
326
|
+
result = IPWhois().lookup("8.8.8.8")
|
|
327
327
|
|
|
328
|
-
assert result["success"] is
|
|
329
|
-
assert
|
|
328
|
+
assert result["success"] is False
|
|
329
|
+
assert "Invalid JSON" in result.get("message", "")
|
|
330
|
+
assert result.get("http_status") == 200
|
|
@@ -1,41 +0,0 @@
|
|
|
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.1] - 2026-05-09
|
|
9
|
-
|
|
10
|
-
### Changed
|
|
11
|
-
|
|
12
|
-
- **Renamed the main class `Client` to `IPWhois`** for consistency with the
|
|
13
|
-
package and brand. The recommended import is now
|
|
14
|
-
`from ipwhois import IPWhois`. The source module moved from
|
|
15
|
-
`src/ipwhois/client.py` to `src/ipwhois/ipwhois.py`, and the test module
|
|
16
|
-
from `tests/test_client.py` to `tests/test_ipwhois.py`. Public behaviour,
|
|
17
|
-
method signatures, constructor arguments, and return shapes are all
|
|
18
|
-
unchanged.
|
|
19
|
-
|
|
20
|
-
### Migration
|
|
21
|
-
|
|
22
|
-
```python
|
|
23
|
-
# Before (1.0.0):
|
|
24
|
-
from ipwhois import Client
|
|
25
|
-
client = Client("YOUR_API_KEY")
|
|
26
|
-
info = client.lookup("8.8.8.8")
|
|
27
|
-
|
|
28
|
-
# After (1.0.1+):
|
|
29
|
-
from ipwhois import IPWhois
|
|
30
|
-
ipwhois = IPWhois("YOUR_API_KEY")
|
|
31
|
-
info = ipwhois.lookup("8.8.8.8")
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
The variable name (`client`, `ipwhois`, anything else) is up to you; only
|
|
35
|
-
the class identifier changed.
|
|
36
|
-
|
|
37
|
-
## [1.0.0] - 2026-05-08
|
|
38
|
-
|
|
39
|
-
### Added
|
|
40
|
-
|
|
41
|
-
- Initial release.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|