ipspot 0.5__py3-none-any.whl → 0.7__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.
ipspot/cli.py CHANGED
@@ -8,9 +8,11 @@ from .ipv6 import get_public_ipv6, get_private_ipv6
8
8
  from .utils import _filter_parameter
9
9
  from .params import IPv4API, IPv6API, PARAMETERS_NAME_MAP
10
10
  from .params import IPSPOT_OVERVIEW, IPSPOT_REPO, IPSPOT_VERSION
11
+ from .params import PUBLIC_IPV4_ERROR, PRIVATE_IPV4_ERROR
12
+ from .params import PUBLIC_IPV6_ERROR, PRIVATE_IPV6_ERROR
11
13
 
12
14
 
13
- def ipspot_info() -> None: # pragma: no cover
15
+ def _print_ipspot_info() -> None: # pragma: no cover
14
16
  """Print ipspot details."""
15
17
  tprint("IPSpot")
16
18
  tprint("V:" + IPSPOT_VERSION)
@@ -18,11 +20,13 @@ def ipspot_info() -> None: # pragma: no cover
18
20
  print("Repo : " + IPSPOT_REPO)
19
21
 
20
22
 
21
- def display_ip_info(ipv4_api: IPv4API = IPv4API.AUTO_SAFE,
22
- ipv6_api: IPv6API = IPv6API.AUTO_SAFE,
23
- geo: bool=False,
24
- timeout: Union[float, Tuple[float, float]]=5,
25
- max_retries: int = 0, retry_delay: float = 1.0) -> None: # pragma: no cover
23
+ def _print_report(ipv4_api: IPv4API,
24
+ ipv6_api: IPv6API,
25
+ geo: bool,
26
+ timeout: Union[float, Tuple[float, float]],
27
+ max_retries: int,
28
+ retry_delay: float,
29
+ backoff_factor: float) -> None: # pragma: no cover
26
30
  """
27
31
  Print collected IP and location data.
28
32
 
@@ -32,20 +36,21 @@ def display_ip_info(ipv4_api: IPv4API = IPv4API.AUTO_SAFE,
32
36
  :param timeout: timeout value for API
33
37
  :param max_retries: number of retries
34
38
  :param retry_delay: delay between retries (in seconds)
39
+ :param backoff_factor: backoff factor
35
40
  """
36
41
  print("Private IP:\n")
37
42
  private_ipv4_result = get_private_ipv4()
38
43
  if private_ipv4_result["status"]:
39
44
  private_ipv4 = private_ipv4_result["data"]["ip"]
40
45
  else:
41
- private_ipv4 = private_ipv4_result["error"]
46
+ private_ipv4 = PRIVATE_IPV4_ERROR
42
47
  print(" IPv4: {private_ipv4}\n".format(private_ipv4=private_ipv4))
43
48
 
44
49
  private_ipv6_result = get_private_ipv6()
45
50
  if private_ipv6_result["status"]:
46
51
  private_ipv6 = private_ipv6_result["data"]["ip"]
47
52
  else:
48
- private_ipv6 = private_ipv6_result["error"]
53
+ private_ipv6 = PRIVATE_IPV6_ERROR
49
54
  print(" IPv6: {private_ipv6}".format(private_ipv6=private_ipv6))
50
55
 
51
56
  public_title = "\nPublic IP"
@@ -59,7 +64,8 @@ def display_ip_info(ipv4_api: IPv4API = IPv4API.AUTO_SAFE,
59
64
  geo=geo,
60
65
  timeout=timeout,
61
66
  max_retries=max_retries,
62
- retry_delay=retry_delay)
67
+ retry_delay=retry_delay,
68
+ backoff_factor=backoff_factor)
63
69
  if public_ipv4_result["status"]:
64
70
  for name, parameter in sorted(public_ipv4_result["data"].items()):
65
71
  print(
@@ -67,7 +73,7 @@ def display_ip_info(ipv4_api: IPv4API = IPv4API.AUTO_SAFE,
67
73
  name=PARAMETERS_NAME_MAP[name],
68
74
  parameter=_filter_parameter(parameter)))
69
75
  else:
70
- print(" Error: {public_ipv4_result[error]}".format(public_ipv4_result=public_ipv4_result))
76
+ print(" Error: {public_ipv4_result}".format(public_ipv4_result=PUBLIC_IPV4_ERROR))
71
77
 
72
78
  print("\n IPv6:\n")
73
79
  public_ipv6_result = get_public_ipv6(
@@ -75,7 +81,8 @@ def display_ip_info(ipv4_api: IPv4API = IPv4API.AUTO_SAFE,
75
81
  geo=geo,
76
82
  timeout=timeout,
77
83
  max_retries=max_retries,
78
- retry_delay=retry_delay)
84
+ retry_delay=retry_delay,
85
+ backoff_factor=backoff_factor)
79
86
  if public_ipv6_result["status"]:
80
87
  for name, parameter in sorted(public_ipv6_result["data"].items()):
81
88
  print(
@@ -83,7 +90,7 @@ def display_ip_info(ipv4_api: IPv4API = IPv4API.AUTO_SAFE,
83
90
  name=PARAMETERS_NAME_MAP[name],
84
91
  parameter=_filter_parameter(parameter)))
85
92
  else:
86
- print(" Error: {public_ipv6_result[error]}".format(public_ipv6_result=public_ipv6_result))
93
+ print(" Error: {public_ipv6_result}".format(public_ipv6_result=PUBLIC_IPV6_ERROR))
87
94
 
88
95
 
89
96
  def main() -> None: # pragma: no cover
@@ -109,20 +116,22 @@ def main() -> None: # pragma: no cover
109
116
  parser.add_argument('--timeout', help='timeout for the API request', type=float, default=5.0)
110
117
  parser.add_argument('--max-retries', help='number of retries', type=int, default=0)
111
118
  parser.add_argument('--retry-delay', help='delay between retries (in seconds)', type=float, default=1.0)
119
+ parser.add_argument('--backoff-factor', help='backoff factor', type=float, default=1.0)
112
120
 
113
121
  args = parser.parse_args()
114
122
  if args.version:
115
123
  print(IPSPOT_VERSION)
116
124
  elif args.info:
117
- ipspot_info()
125
+ _print_ipspot_info()
118
126
  else:
119
127
  ipv4_api = IPv4API(args.ipv4_api)
120
128
  ipv6_api = IPv6API(args.ipv6_api)
121
129
  geo = not args.no_geo
122
- display_ip_info(
130
+ _print_report(
123
131
  ipv4_api=ipv4_api,
124
132
  ipv6_api=ipv6_api,
125
133
  geo=geo,
126
134
  timeout=args.timeout,
127
135
  max_retries=args.max_retries,
128
- retry_delay=args.retry_delay)
136
+ retry_delay=args.retry_delay,
137
+ backoff_factor=args.backoff_factor)
ipspot/ipv4.py CHANGED
@@ -3,69 +3,9 @@
3
3
  import ipaddress
4
4
  import socket
5
5
  from typing import Union, Dict, List, Tuple
6
- import requests
7
- from requests.adapters import HTTPAdapter
8
- from urllib3.poolmanager import PoolManager
9
- from .utils import is_loopback, _get_json_standard, _attempt_with_retries
10
- from .params import REQUEST_HEADERS, IPv4API
11
-
12
-
13
- class IPv4HTTPAdapter(HTTPAdapter):
14
- """A custom HTTPAdapter that enforces the use of IPv4 for DNS resolution during HTTP(S) requests using the requests library."""
15
-
16
- def init_poolmanager(self, connections: int, maxsize: int, block: bool = False, **kwargs: dict) -> None:
17
- """
18
- Initialize the connection pool manager using a temporary override of socket.getaddrinfo to ensure only IPv4 addresses are used.
19
-
20
- :param connections: the number of connection pools to cache
21
- :param maxsize: the maximum number of connections to save in the pool
22
- :param block: whether the connections should block when reaching the max size
23
- :param kwargs: additional keyword arguments for the PoolManager
24
- """
25
- self.poolmanager = PoolManager(
26
- num_pools=connections,
27
- maxsize=maxsize,
28
- block=block,
29
- socket_options=self._ipv4_socket_options(),
30
- **kwargs
31
- )
32
-
33
- def _ipv4_socket_options(self) -> list:
34
- """
35
- Temporarily patches socket.getaddrinfo to filter only IPv4 addresses (AF_INET).
36
-
37
- :return: an empty list of socket options; DNS patching occurs here
38
- """
39
- original_getaddrinfo = socket.getaddrinfo
40
-
41
- def ipv4_only_getaddrinfo(*args: list, **kwargs: dict) -> List[Tuple]:
42
- results = original_getaddrinfo(*args, **kwargs)
43
- return [res for res in results if res[0] == socket.AF_INET]
44
-
45
- self._original_getaddrinfo = socket.getaddrinfo
46
- socket.getaddrinfo = ipv4_only_getaddrinfo
47
-
48
- return []
49
-
50
- def __del__(self) -> None:
51
- """Restores the original socket.getaddrinfo function upon adapter deletion."""
52
- if hasattr(self, "_original_getaddrinfo"):
53
- socket.getaddrinfo = self._original_getaddrinfo
54
-
55
-
56
- def _get_json_ipv4_forced(url: str, timeout: Union[float, Tuple[float, float]]) -> dict:
57
- """
58
- Send GET request with forced IPv4 using IPv4HTTPAdapter that returns JSON response.
59
-
60
- :param url: API url
61
- :param timeout: timeout value for API
62
- """
63
- with requests.Session() as session:
64
- session.mount("http://", IPv4HTTPAdapter())
65
- session.mount("https://", IPv4HTTPAdapter())
66
- response = session.get(url, headers=REQUEST_HEADERS, timeout=timeout)
67
- response.raise_for_status()
68
- return response.json()
6
+ from .utils import is_loopback, _attempt_with_retries
7
+ from .utils import _get_json_standard, _get_json_force_ip
8
+ from .params import IPv4API
69
9
 
70
10
 
71
11
  def is_ipv4(ip: str) -> bool:
@@ -96,8 +36,8 @@ def get_private_ipv4() -> Dict[str, Union[bool, Dict[str, str], str]]:
96
36
  return {"status": False, "error": str(e)}
97
37
 
98
38
 
99
- def _ip_sb_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
100
- =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
39
+ def _ip_sb_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]]
40
+ ) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
101
41
  """
102
42
  Get public IP and geolocation using ip.sb.
103
43
 
@@ -124,8 +64,8 @@ def _ip_sb_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
124
64
  return {"status": False, "error": str(e)}
125
65
 
126
66
 
127
- def _ipleak_net_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
128
- =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
67
+ def _ipleak_net_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]]
68
+ ) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
129
69
  """
130
70
  Get public IP and geolocation using ipleak.net.
131
71
 
@@ -152,8 +92,8 @@ def _ipleak_net_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
152
92
  return {"status": False, "error": str(e)}
153
93
 
154
94
 
155
- def _my_ip_io_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
156
- =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
95
+ def _my_ip_io_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]]
96
+ ) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
157
97
  """
158
98
  Get public IP and geolocation using my-ip.io.
159
99
 
@@ -180,8 +120,9 @@ def _my_ip_io_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
180
120
  return {"status": False, "error": str(e)}
181
121
 
182
122
 
183
- def _ifconfig_co_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]] # very low rate limit
184
- =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
123
+ # very low rate limit
124
+ def _ifconfig_co_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]]
125
+ ) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
185
126
  """
186
127
  Get public IP and geolocation using ifconfig.co.
187
128
 
@@ -189,7 +130,7 @@ def _ifconfig_co_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]
189
130
  :param timeout: timeout value for API
190
131
  """
191
132
  try:
192
- data = _get_json_ipv4_forced(url="https://ifconfig.co/json", timeout=timeout)
133
+ data = _get_json_force_ip(url="https://ifconfig.co/json", timeout=timeout, version="ipv4")
193
134
  result = {"status": True, "data": {"ip": data["ip"], "api": "ifconfig.co"}}
194
135
  if geo:
195
136
  geo_data = {
@@ -208,8 +149,8 @@ def _ifconfig_co_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]
208
149
  return {"status": False, "error": str(e)}
209
150
 
210
151
 
211
- def _ipapi_co_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
212
- =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
152
+ def _ipapi_co_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]]
153
+ ) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
213
154
  """
214
155
  Get public IP and geolocation using ipapi.co.
215
156
 
@@ -217,7 +158,7 @@ def _ipapi_co_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
217
158
  :param timeout: timeout value for API
218
159
  """
219
160
  try:
220
- data = _get_json_ipv4_forced(url="https://ipapi.co/json/", timeout=timeout)
161
+ data = _get_json_force_ip(url="https://ipapi.co/json/", timeout=timeout, version="ipv4")
221
162
  result = {"status": True, "data": {"ip": data["ip"], "api": "ipapi.co"}}
222
163
  if geo:
223
164
  geo_data = {
@@ -236,8 +177,8 @@ def _ipapi_co_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
236
177
  return {"status": False, "error": str(e)}
237
178
 
238
179
 
239
- def _ip_api_com_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
240
- =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
180
+ def _ip_api_com_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]]
181
+ ) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
241
182
  """
242
183
  Get public IP and geolocation using ip-api.com.
243
184
 
@@ -245,7 +186,7 @@ def _ip_api_com_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
245
186
  :param timeout: timeout value for API
246
187
  """
247
188
  try:
248
- data = _get_json_ipv4_forced(url="http://ip-api.com/json/", timeout=timeout)
189
+ data = _get_json_force_ip(url="http://ip-api.com/json/", timeout=timeout, version="ipv4")
249
190
  if data.get("status") != "success":
250
191
  return {"status": False, "error": "ip-api lookup failed"}
251
192
  result = {"status": True, "data": {"ip": data["query"], "api": "ip-api.com"}}
@@ -266,8 +207,8 @@ def _ip_api_com_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
266
207
  return {"status": False, "error": str(e)}
267
208
 
268
209
 
269
- def _ipinfo_io_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
270
- =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
210
+ def _ipinfo_io_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]]
211
+ ) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
271
212
  """
272
213
  Get public IP and geolocation using ipinfo.io.
273
214
 
@@ -275,7 +216,7 @@ def _ipinfo_io_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
275
216
  :param timeout: timeout value for API
276
217
  """
277
218
  try:
278
- data = _get_json_ipv4_forced(url="https://ipinfo.io/json", timeout=timeout)
219
+ data = _get_json_force_ip(url="https://ipinfo.io/json", timeout=timeout, version="ipv4")
279
220
  result = {"status": True, "data": {"ip": data["ip"], "api": "ipinfo.io"}}
280
221
  if geo:
281
222
  loc = data.get("loc", "").split(",")
@@ -295,8 +236,8 @@ def _ipinfo_io_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
295
236
  return {"status": False, "error": str(e)}
296
237
 
297
238
 
298
- def _reallyfreegeoip_org_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
299
- =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
239
+ def _reallyfreegeoip_org_ipv4(
240
+ geo: bool, timeout: Union[float, Tuple[float, float]]) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
300
241
  """
301
242
  Get public IP and geolocation using reallyfreegeoip.org.
302
243
 
@@ -304,7 +245,7 @@ def _reallyfreegeoip_org_ipv4(geo: bool=False, timeout: Union[float, Tuple[float
304
245
  :param timeout: timeout value for API
305
246
  """
306
247
  try:
307
- data = _get_json_ipv4_forced(url="https://reallyfreegeoip.org/json/", timeout=timeout)
248
+ data = _get_json_force_ip(url="https://reallyfreegeoip.org/json/", timeout=timeout, version="ipv4")
308
249
  result = {"status": True, "data": {"ip": data["ip"], "api": "reallyfreegeoip.org"}}
309
250
  if geo:
310
251
  geo_data = {
@@ -323,8 +264,8 @@ def _reallyfreegeoip_org_ipv4(geo: bool=False, timeout: Union[float, Tuple[float
323
264
  return {"status": False, "error": str(e)}
324
265
 
325
266
 
326
- def _ident_me_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
327
- =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
267
+ def _ident_me_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]]
268
+ ) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
328
269
  """
329
270
  Get public IP and geolocation using ident.me.
330
271
 
@@ -351,8 +292,8 @@ def _ident_me_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
351
292
  return {"status": False, "error": str(e)}
352
293
 
353
294
 
354
- def _tnedi_me_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
355
- =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
295
+ def _tnedi_me_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]]
296
+ ) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
356
297
  """
357
298
  Get public IP and geolocation using tnedi.me.
358
299
 
@@ -379,7 +320,7 @@ def _tnedi_me_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
379
320
  return {"status": False, "error": str(e)}
380
321
 
381
322
 
382
- def _myip_la_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]=5
323
+ def _myip_la_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]]
383
324
  ) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
384
325
  """
385
326
  Get public IP and geolocation using myip.la.
@@ -388,7 +329,7 @@ def _myip_la_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]=5
388
329
  :param timeout: timeout value for API
389
330
  """
390
331
  try:
391
- data = _get_json_ipv4_forced(url="https://api.myip.la/en?json", timeout=timeout)
332
+ data = _get_json_force_ip(url="https://api.myip.la/en?json", timeout=timeout, version="ipv4")
392
333
  result = {"status": True, "data": {"ip": data["ip"], "api": "myip.la"}}
393
334
  if geo:
394
335
  loc = data.get("location", {})
@@ -408,7 +349,7 @@ def _myip_la_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]=5
408
349
  return {"status": False, "error": str(e)}
409
350
 
410
351
 
411
- def _freeipapi_com_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]=5
352
+ def _freeipapi_com_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]]
412
353
  ) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
413
354
  """
414
355
  Get public IP and geolocation using freeipapi.com.
@@ -417,8 +358,9 @@ def _freeipapi_com_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, floa
417
358
  :param timeout: timeout value for API
418
359
  """
419
360
  try:
420
- data = _get_json_ipv4_forced(url="https://freeipapi.com/api/json", timeout=timeout)
361
+ data = _get_json_force_ip(url="https://freeipapi.com/api/json", timeout=timeout, version="ipv4")
421
362
  result = {"status": True, "data": {"ip": data["ipAddress"], "api": "freeipapi.com"}}
363
+ tzs = data.get("timeZones", [])
422
364
  if geo:
423
365
  geo_data = {
424
366
  "city": data.get("cityName"),
@@ -427,8 +369,8 @@ def _freeipapi_com_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, floa
427
369
  "country_code": data.get("countryCode"),
428
370
  "latitude": data.get("latitude"),
429
371
  "longitude": data.get("longitude"),
430
- "organization": None,
431
- "timezone": data.get("timeZone")
372
+ "organization": data.get("asnOrganization"),
373
+ "timezone": tzs[0] if len(tzs) > 0 else None
432
374
  }
433
375
  result["data"].update(geo_data)
434
376
  return result
@@ -436,7 +378,7 @@ def _freeipapi_com_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, floa
436
378
  return {"status": False, "error": str(e)}
437
379
 
438
380
 
439
- def _ipquery_io_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]=5
381
+ def _ipquery_io_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]]
440
382
  ) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
441
383
  """
442
384
  Get public IP and geolocation using ipquery.io.
@@ -445,7 +387,7 @@ def _ipquery_io_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
445
387
  :param timeout: timeout value for API
446
388
  """
447
389
  try:
448
- data = _get_json_ipv4_forced(url="https://api.ipquery.io/?format=json", timeout=timeout)
390
+ data = _get_json_force_ip(url="https://api.ipquery.io/?format=json", timeout=timeout, version="ipv4")
449
391
  result = {"status": True, "data": {"ip": data["ip"], "api": "ipquery.io"}}
450
392
  if geo:
451
393
  loc = data.get("location", {})
@@ -466,7 +408,7 @@ def _ipquery_io_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
466
408
  return {"status": False, "error": str(e)}
467
409
 
468
410
 
469
- def _ipwho_is_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]=5
411
+ def _ipwho_is_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]]
470
412
  ) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
471
413
  """
472
414
  Get public IP and geolocation using ipwho.is.
@@ -475,7 +417,7 @@ def _ipwho_is_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]=5
475
417
  :param timeout: timeout value for API
476
418
  """
477
419
  try:
478
- data = _get_json_ipv4_forced(url="https://ipwho.is", timeout=timeout)
420
+ data = _get_json_force_ip(url="https://ipwho.is", timeout=timeout, version="ipv4")
479
421
  result = {"status": True, "data": {"ip": data["ip"], "api": "ipwho.is"}}
480
422
  if geo:
481
423
  connection = data.get("connection", {})
@@ -496,7 +438,7 @@ def _ipwho_is_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]=5
496
438
  return {"status": False, "error": str(e)}
497
439
 
498
440
 
499
- def _wtfismyip_com_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]=5
441
+ def _wtfismyip_com_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]]
500
442
  ) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
501
443
  """
502
444
  Get public IP and geolocation using wtfismyip.com.
@@ -606,7 +548,8 @@ IPV4_API_MAP = {
606
548
  def get_public_ipv4(api: IPv4API=IPv4API.AUTO_SAFE, geo: bool=False,
607
549
  timeout: Union[float, Tuple[float, float]]=5,
608
550
  max_retries: int = 0,
609
- retry_delay: float = 1.0) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
551
+ retry_delay: float = 1.0,
552
+ backoff_factor: float = 1.0) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
610
553
  """
611
554
  Get public IPv4 and geolocation info based on the selected API.
612
555
 
@@ -615,6 +558,7 @@ def get_public_ipv4(api: IPv4API=IPv4API.AUTO_SAFE, geo: bool=False,
615
558
  :param timeout: timeout value for API
616
559
  :param max_retries: number of retries
617
560
  :param retry_delay: delay between retries (in seconds)
561
+ :param backoff_factor: backoff factor
618
562
  """
619
563
  if api in [IPv4API.AUTO, IPv4API.AUTO_SAFE]:
620
564
  for _, api_data in IPV4_API_MAP.items():
@@ -625,6 +569,7 @@ def get_public_ipv4(api: IPv4API=IPv4API.AUTO_SAFE, geo: bool=False,
625
569
  func=func,
626
570
  max_retries=max_retries,
627
571
  retry_delay=retry_delay,
572
+ backoff_factor=backoff_factor,
628
573
  geo=geo,
629
574
  timeout=timeout)
630
575
  if result["status"]:
@@ -638,6 +583,7 @@ def get_public_ipv4(api: IPv4API=IPv4API.AUTO_SAFE, geo: bool=False,
638
583
  func=func,
639
584
  max_retries=max_retries,
640
585
  retry_delay=retry_delay,
586
+ backoff_factor=backoff_factor,
641
587
  geo=geo,
642
588
  timeout=timeout)
643
589
  return {"status": False, "error": "Unsupported API: {api}".format(api=api)}
ipspot/ipv6.py CHANGED
@@ -4,7 +4,8 @@ import ipaddress
4
4
  import socket
5
5
  from typing import Union, Dict, List, Tuple
6
6
  from .params import IPv6API
7
- from .utils import is_loopback, _get_json_standard, _attempt_with_retries
7
+ from .utils import is_loopback, _attempt_with_retries
8
+ from .utils import _get_json_standard, _get_json_force_ip
8
9
 
9
10
 
10
11
  def is_ipv6(ip: str) -> bool:
@@ -36,8 +37,8 @@ def get_private_ipv6() -> Dict[str, Union[bool, Dict[str, str], str]]:
36
37
  return {"status": False, "error": str(e)}
37
38
 
38
39
 
39
- def _ip_sb_ipv6(geo: bool=False, timeout: Union[float, Tuple[float, float]]
40
- =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
40
+ def _ip_sb_ipv6(geo: bool, timeout: Union[float, Tuple[float, float]]
41
+ ) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
41
42
  """
42
43
  Get public IP and geolocation using ip.sb.
43
44
 
@@ -64,8 +65,8 @@ def _ip_sb_ipv6(geo: bool=False, timeout: Union[float, Tuple[float, float]]
64
65
  return {"status": False, "error": str(e)}
65
66
 
66
67
 
67
- def _ident_me_ipv6(geo: bool=False, timeout: Union[float, Tuple[float, float]]
68
- =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
68
+ def _ident_me_ipv6(geo: bool, timeout: Union[float, Tuple[float, float]]
69
+ ) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
69
70
  """
70
71
  Get public IP and geolocation using ident.me.
71
72
 
@@ -92,8 +93,8 @@ def _ident_me_ipv6(geo: bool=False, timeout: Union[float, Tuple[float, float]]
92
93
  return {"status": False, "error": str(e)}
93
94
 
94
95
 
95
- def _tnedi_me_ipv6(geo: bool=False, timeout: Union[float, Tuple[float, float]]
96
- =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
96
+ def _tnedi_me_ipv6(geo: bool, timeout: Union[float, Tuple[float, float]]
97
+ ) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
97
98
  """
98
99
  Get public IP and geolocation using tnedi.me.
99
100
 
@@ -120,8 +121,8 @@ def _tnedi_me_ipv6(geo: bool=False, timeout: Union[float, Tuple[float, float]]
120
121
  return {"status": False, "error": str(e)}
121
122
 
122
123
 
123
- def _ipleak_net_ipv6(geo: bool=False, timeout: Union[float, Tuple[float, float]]
124
- =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
124
+ def _ipleak_net_ipv6(geo: bool, timeout: Union[float, Tuple[float, float]]
125
+ ) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
125
126
  """
126
127
  Get public IP and geolocation using ipleak.net.
127
128
 
@@ -148,8 +149,8 @@ def _ipleak_net_ipv6(geo: bool=False, timeout: Union[float, Tuple[float, float]]
148
149
  return {"status": False, "error": str(e)}
149
150
 
150
151
 
151
- def _my_ip_io_ipv6(geo: bool=False, timeout: Union[float, Tuple[float, float]]
152
- =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
152
+ def _my_ip_io_ipv6(geo: bool, timeout: Union[float, Tuple[float, float]]
153
+ ) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
153
154
  """
154
155
  Get public IP and geolocation using my-ip.io.
155
156
 
@@ -176,6 +177,121 @@ def _my_ip_io_ipv6(geo: bool=False, timeout: Union[float, Tuple[float, float]]
176
177
  return {"status": False, "error": str(e)}
177
178
 
178
179
 
180
+ # very low rate limit
181
+ def _ifconfig_co_ipv6(geo: bool, timeout: Union[float, Tuple[float, float]]
182
+ ) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
183
+ """
184
+ Get public IP and geolocation using ifconfig.co.
185
+
186
+ :param geo: geolocation flag
187
+ :param timeout: timeout value for API
188
+ """
189
+ try:
190
+ data = _get_json_force_ip(url="https://ifconfig.co/json", timeout=timeout, version="ipv6")
191
+ result = {"status": True, "data": {"ip": data["ip"], "api": "ifconfig.co"}}
192
+ if geo:
193
+ geo_data = {
194
+ "city": data.get("city"),
195
+ "region": data.get("region_name"),
196
+ "country": data.get("country"),
197
+ "country_code": data.get("country_iso"),
198
+ "latitude": data.get("latitude"),
199
+ "longitude": data.get("longitude"),
200
+ "organization": data.get("asn_org"),
201
+ "timezone": data.get("time_zone")
202
+ }
203
+ result["data"].update(geo_data)
204
+ return result
205
+ except Exception as e:
206
+ return {"status": False, "error": str(e)}
207
+
208
+
209
+ def _reallyfreegeoip_org_ipv6(
210
+ geo: bool, timeout: Union[float, Tuple[float, float]]) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
211
+ """
212
+ Get public IP and geolocation using reallyfreegeoip.org.
213
+
214
+ :param geo: geolocation flag
215
+ :param timeout: timeout value for API
216
+ """
217
+ try:
218
+ data = _get_json_force_ip(url="https://reallyfreegeoip.org/json/", timeout=timeout, version="ipv6")
219
+ result = {"status": True, "data": {"ip": data["ip"], "api": "reallyfreegeoip.org"}}
220
+ if geo:
221
+ geo_data = {
222
+ "city": data.get("city"),
223
+ "region": data.get("region_name"),
224
+ "country": data.get("country_name"),
225
+ "country_code": data.get("country_code"),
226
+ "latitude": data.get("latitude"),
227
+ "longitude": data.get("longitude"),
228
+ "organization": None, # does not provide organization
229
+ "timezone": data.get("time_zone")
230
+ }
231
+ result["data"].update(geo_data)
232
+ return result
233
+ except Exception as e:
234
+ return {"status": False, "error": str(e)}
235
+
236
+
237
+ def _myip_la_ipv6(geo: bool, timeout: Union[float, Tuple[float, float]]
238
+ ) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
239
+ """
240
+ Get public IP and geolocation using myip.la.
241
+
242
+ :param geo: geolocation flag
243
+ :param timeout: timeout value for API
244
+ """
245
+ try:
246
+ data = _get_json_force_ip(url="https://api.myip.la/en?json", timeout=timeout, version="ipv6")
247
+ result = {"status": True, "data": {"ip": data["ip"], "api": "myip.la"}}
248
+ if geo:
249
+ loc = data.get("location", {})
250
+ geo_data = {
251
+ "city": loc.get("city"),
252
+ "region": loc.get("province"),
253
+ "country": loc.get("country_name"),
254
+ "country_code": loc.get("country_code"),
255
+ "latitude": float(loc.get("latitude")) if loc.get("latitude") else None,
256
+ "longitude": float(loc.get("longitude")) if loc.get("longitude") else None,
257
+ "organization": None,
258
+ "timezone": None
259
+ }
260
+ result["data"].update(geo_data)
261
+ return result
262
+ except Exception as e:
263
+ return {"status": False, "error": str(e)}
264
+
265
+
266
+ def _freeipapi_com_ipv6(geo: bool, timeout: Union[float, Tuple[float, float]]
267
+ ) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
268
+ """
269
+ Get public IP and geolocation using freeipapi.com.
270
+
271
+ :param geo: geolocation flag
272
+ :param timeout: timeout value for API
273
+ """
274
+ try:
275
+ data = _get_json_force_ip(url="https://free.freeipapi.com/api/json", timeout=timeout, version="ipv6")
276
+ result = {"status": True, "data": {"ip": data["ipAddress"], "api": "freeipapi.com"}}
277
+ tzs = data.get("timeZones", [])
278
+ if geo:
279
+ geo_data = {
280
+ "city": data.get("cityName"),
281
+ "region": data.get("regionName"),
282
+ "country": data.get("countryName"),
283
+ "country_code": data.get("countryCode"),
284
+ "latitude": data.get("latitude"),
285
+ "longitude": data.get("longitude"),
286
+ "organization": data.get("asnOrganization"),
287
+ "timezone": tzs[0] if len(tzs) > 0 else None
288
+ }
289
+ result["data"].update(geo_data)
290
+ return result
291
+ except Exception as e:
292
+ return {"status": False, "error": str(e)}
293
+
294
+
179
295
  IPV6_API_MAP = {
180
296
  IPv6API.IP_SB: {
181
297
  "thread_safe": True,
@@ -202,13 +318,34 @@ IPV6_API_MAP = {
202
318
  "geo": True,
203
319
  "function": _my_ip_io_ipv6
204
320
  },
321
+ IPv6API.IFCONFIG_CO: {
322
+ "thread_safe": False,
323
+ "geo": True,
324
+ "function": _ifconfig_co_ipv6
325
+ },
326
+ IPv6API.REALLYFREEGEOIP_ORG: {
327
+ "thread_safe": False,
328
+ "geo": True,
329
+ "function": _reallyfreegeoip_org_ipv6
330
+ },
331
+ IPv6API.MYIP_LA: {
332
+ "thread_safe": False,
333
+ "geo": True,
334
+ "function": _myip_la_ipv6
335
+ },
336
+ IPv6API.FREEIPAPI_COM: {
337
+ "thread_safe": False,
338
+ "geo": True,
339
+ "function": _freeipapi_com_ipv6
340
+ }
205
341
  }
206
342
 
207
343
 
208
344
  def get_public_ipv6(api: IPv6API=IPv6API.AUTO_SAFE, geo: bool=False,
209
345
  timeout: Union[float, Tuple[float, float]]=5,
210
346
  max_retries: int = 0,
211
- retry_delay: float = 1.0) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
347
+ retry_delay: float = 1.0,
348
+ backoff_factor: float = 1.0) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
212
349
  """
213
350
  Get public IPv6 and geolocation info based on the selected API.
214
351
 
@@ -217,6 +354,7 @@ def get_public_ipv6(api: IPv6API=IPv6API.AUTO_SAFE, geo: bool=False,
217
354
  :param timeout: timeout value for API
218
355
  :param max_retries: number of retries
219
356
  :param retry_delay: delay between retries (in seconds)
357
+ :param backoff_factor: backoff factor
220
358
  """
221
359
  if api in [IPv6API.AUTO, IPv6API.AUTO_SAFE]:
222
360
  for _, api_data in IPV6_API_MAP.items():
@@ -227,6 +365,7 @@ def get_public_ipv6(api: IPv6API=IPv6API.AUTO_SAFE, geo: bool=False,
227
365
  func=func,
228
366
  max_retries=max_retries,
229
367
  retry_delay=retry_delay,
368
+ backoff_factor=backoff_factor,
230
369
  geo=geo,
231
370
  timeout=timeout)
232
371
  if result["status"]:
@@ -240,6 +379,7 @@ def get_public_ipv6(api: IPv6API=IPv6API.AUTO_SAFE, geo: bool=False,
240
379
  func=func,
241
380
  max_retries=max_retries,
242
381
  retry_delay=retry_delay,
382
+ backoff_factor=backoff_factor,
243
383
  geo=geo,
244
384
  timeout=timeout)
245
385
  return {"status": False, "error": "Unsupported API: {api}".format(api=api)}
ipspot/params.py CHANGED
@@ -2,7 +2,7 @@
2
2
  """ipspot params."""
3
3
  from enum import Enum
4
4
 
5
- IPSPOT_VERSION = "0.5"
5
+ IPSPOT_VERSION = "0.7"
6
6
 
7
7
  IPSPOT_OVERVIEW = '''
8
8
  IPSpot is a Python library for retrieving the current system's IP address and location information.
@@ -50,6 +50,10 @@ class IPv6API(Enum):
50
50
  TNEDI_ME = "tnedi.me"
51
51
  IPLEAK_NET = "ipleak.net"
52
52
  MY_IP_IO = "my-ip.io"
53
+ IFCONFIG_CO = "ifconfig.co"
54
+ REALLYFREEGEOIP_ORG = "reallyfreegeoip.org"
55
+ MYIP_LA = "myip.la"
56
+ FREEIPAPI_COM = "freeipapi.com"
53
57
 
54
58
 
55
59
  PARAMETERS_NAME_MAP = {
@@ -64,3 +68,8 @@ PARAMETERS_NAME_MAP = {
64
68
  "organization": "Organization",
65
69
  "api": "API"
66
70
  }
71
+
72
+ PUBLIC_IPV4_ERROR = "Unable to retrieve public IPv4 information."
73
+ PRIVATE_IPV4_ERROR = "Unable to retrieve private IPv4 address."
74
+ PUBLIC_IPV6_ERROR = "Unable to retrieve public IPv6 information."
75
+ PRIVATE_IPV6_ERROR = "Unable to retrieve private IPv6 address."
ipspot/utils.py CHANGED
@@ -2,31 +2,99 @@
2
2
  """ipspot utils."""
3
3
  import time
4
4
  import ipaddress
5
+ import socket
5
6
  import requests
7
+ from requests.adapters import HTTPAdapter
6
8
  from typing import Callable, Dict
7
- from typing import Union, Tuple, Any
9
+ from typing import Union, Tuple, Any, List
8
10
  from .params import REQUEST_HEADERS
9
11
 
10
12
 
13
+ class ForceIPHTTPAdapter(HTTPAdapter):
14
+ """A custom HTTPAdapter that enforces IPv4 or IPv6 DNS resolution for HTTP(S) requests."""
15
+
16
+ def __init__(self, version: str = "ipv4", *args: list, **kwargs: dict) -> None:
17
+ """
18
+ Initialize the adapter with the desired IP version.
19
+
20
+ :param version: 'ipv4' or 'ipv6' to select address family
21
+ :param args: additional list arguments for the HTTPAdapter
22
+ :param kwargs: additional keyword arguments for the HTTPAdapter
23
+ """
24
+ self.version = version.lower()
25
+ if self.version not in ("ipv4", "ipv6"):
26
+ raise ValueError("version must be either 'ipv4' or 'ipv6'")
27
+ super().__init__(*args, **kwargs)
28
+
29
+ def send(self, *args: list, **kwargs: dict) -> Any:
30
+ """
31
+ Override send method to apply the monkey patch only during the request.
32
+
33
+ :param args: additional list arguments for the send method
34
+ :param kwargs: additional keyword arguments for the send method
35
+ """
36
+ family = socket.AF_INET if self.version == "ipv4" else socket.AF_INET6
37
+ original_getaddrinfo = socket.getaddrinfo
38
+
39
+ def filtered_getaddrinfo(*gargs: list, **gkwargs: dict) -> List[Tuple]:
40
+ """
41
+ Filter getaddrinfo.
42
+
43
+ :param gargs: additional list arguments for the original_getaddrinfo function
44
+ :param gkwargs: additional keyword arguments for the original_getaddrinfo function
45
+ """
46
+ results = original_getaddrinfo(*gargs, **gkwargs)
47
+ return [res for res in results if res[0] == family]
48
+
49
+ socket.getaddrinfo = filtered_getaddrinfo
50
+ try:
51
+ response = super().send(*args, **kwargs)
52
+ finally:
53
+ socket.getaddrinfo = original_getaddrinfo
54
+ return response
55
+
56
+
57
+ def _get_json_force_ip(url: str, timeout: Union[float, Tuple[float, float]],
58
+ version: str = "ipv4") -> dict:
59
+ """
60
+ Send GET request with forced IPv4/IPv6 using ForceIPHTTPAdapter that returns JSON response.
61
+
62
+ :param url: API url
63
+ :param timeout: timeout value for API
64
+ :param version: 'ipv4' or 'ipv6' to select address family
65
+ """
66
+ with requests.Session() as session:
67
+ session.mount("http://", ForceIPHTTPAdapter(version=version))
68
+ session.mount("https://", ForceIPHTTPAdapter(version=version))
69
+ response = session.get(url, headers=REQUEST_HEADERS, timeout=timeout)
70
+ response.raise_for_status()
71
+ return response.json()
72
+
73
+
11
74
  def _attempt_with_retries(
12
75
  func: Callable,
13
76
  max_retries: int,
14
- retry_delay: float, **kwargs: dict) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
77
+ retry_delay: float,
78
+ backoff_factor: float,
79
+ **kwargs: dict) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
15
80
  """
16
81
  Attempt a function call with retries and delay.
17
82
 
18
83
  :param func: function to execute
19
84
  :param max_retries: number of retries
20
85
  :param retry_delay: delay between retries (in seconds)
86
+ :param backoff_factor: backoff factor
21
87
  :param kwargs: keyword arguments to pass to the function
22
88
  """
23
89
  max_retries = max(0, max_retries)
24
90
  result = {"status": False, "error": ""}
91
+ next_delay = retry_delay
25
92
  for attempt in range(max_retries + 1):
26
93
  result = func(**kwargs)
27
94
  if result["status"]:
28
95
  break
29
- time.sleep(retry_delay)
96
+ time.sleep(next_delay)
97
+ next_delay *= backoff_factor
30
98
  return result
31
99
 
32
100
 
@@ -1,15 +1,15 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ipspot
3
- Version: 0.5
3
+ Version: 0.7
4
4
  Summary: IPSpot: A Python Tool to Fetch the System's IP Address
5
5
  Home-page: https://github.com/openscilab/ipspot
6
- Download-URL: https://github.com/openscilab/ipspot/tarball/v0.5
6
+ Download-URL: https://github.com/openscilab/ipspot/tarball/v0.7
7
7
  Author: IPSpot Development Team
8
8
  Author-email: ipspot@openscilab.com
9
9
  License: MIT
10
10
  Project-URL: Source, https://github.com/openscilab/ipspot
11
11
  Keywords: ip ipv4 geo geolocation network location ipspot cli
12
- Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Development Status :: 4 - Beta
13
13
  Classifier: Natural Language :: English
14
14
  Classifier: License :: OSI Approved :: MIT License
15
15
  Classifier: Operating System :: OS Independent
@@ -20,6 +20,7 @@ Classifier: Programming Language :: Python :: 3.10
20
20
  Classifier: Programming Language :: Python :: 3.11
21
21
  Classifier: Programming Language :: Python :: 3.12
22
22
  Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
23
24
  Classifier: Intended Audience :: Developers
24
25
  Classifier: Intended Audience :: Education
25
26
  Classifier: Intended Audience :: End Users/Desktop
@@ -52,6 +53,7 @@ Dynamic: summary
52
53
  <img src="https://github.com/openscilab/ipspot/raw/main/otherfiles/logo.png" width="350">
53
54
  <h1>IPSpot: A Python Tool to Fetch the System's IP Address</h1>
54
55
  <br/>
56
+ <a href="https://codecov.io/gh/openscilab/ipspot"><img src="https://codecov.io/gh/openscilab/ipspot/graph/badge.svg?token=XCFKASULS8"></a>
55
57
  <a href="https://badge.fury.io/py/ipspot"><img src="https://badge.fury.io/py/ipspot.svg" alt="PyPI version"></a>
56
58
  <a href="https://www.python.org/"><img src="https://img.shields.io/badge/built%20with-Python3-green.svg" alt="built with Python3"></a>
57
59
  <a href="https://github.com/openscilab/ipspot"><img alt="GitHub repo size" src="https://img.shields.io/github/repo-size/openscilab/ipspot"></a>
@@ -102,13 +104,13 @@ Dynamic: summary
102
104
  ## Installation
103
105
 
104
106
  ### Source Code
105
- - Download [Version 0.5](https://github.com/openscilab/ipspot/archive/v0.5.zip) or [Latest Source](https://github.com/openscilab/ipspot/archive/dev.zip)
107
+ - Download [Version 0.7](https://github.com/openscilab/ipspot/archive/v0.7.zip) or [Latest Source](https://github.com/openscilab/ipspot/archive/dev.zip)
106
108
  - `pip install .`
107
109
 
108
110
  ### PyPI
109
111
 
110
112
  - Check [Python Packaging User Guide](https://packaging.python.org/installing/)
111
- - `pip install ipspot==0.5`
113
+ - `pip install ipspot==0.7`
112
114
 
113
115
 
114
116
  ## Usage
@@ -123,7 +125,7 @@ Dynamic: summary
123
125
  {'status': True, 'data': {'ip': 'xx.xx.xx.xx', 'api': 'ip-api.com'}}
124
126
  >>> get_public_ipv4(api=IPv4API.IP_API_COM, geo=True, timeout=10)
125
127
  {'data': {'country_code': 'GB', 'latitude': 50.9097, 'longitude': -1.4043, 'api': 'ip-api.com', 'country': 'United Kingdom', 'timezone': 'Europe/London', 'organization': '', 'region': 'England', 'ip': 'xx.xx.xx.xx', 'city': 'Southampton'}, 'status': True}
126
- >>> get_public_ipv4(api=IPv4API.IP_API_COM, geo=True, timeout=10, max_retries=5, retry_delay=4)
128
+ >>> get_public_ipv4(api=IPv4API.IP_API_COM, geo=True, timeout=10, max_retries=5, retry_delay=4, backoff_factor=1.2)
127
129
  {'data': {'country_code': 'GB', 'latitude': 50.9097, 'longitude': -1.4043, 'api': 'ip-api.com', 'country': 'United Kingdom', 'timezone': 'Europe/London', 'organization': '', 'region': 'England', 'ip': 'xx.xx.xx.xx', 'city': 'Southampton'}, 'status': True}
128
130
  ```
129
131
 
@@ -143,7 +145,7 @@ Dynamic: summary
143
145
  {'data': {'api': 'ip.sb', 'ip': 'xx:xx:xx:xx::xx'}, 'status': True}
144
146
  >>> get_public_ipv6(api=IPv6API.IP_SB, geo=True, timeout=10)
145
147
  {'data': {'latitude': 51.2993, 'region': None, 'city': None, 'country_code': 'DE', 'api': 'ip.sb', 'longitude': 9.491, 'country': 'Germany', 'organization': 'Hetzner Online', 'timezone': 'Europe/Berlin', 'ip': 'xx:xx:xx:xx::xx'}, 'status': True}
146
- >>> get_public_ipv6(api=IPv6API.IP_SB, geo=True, timeout=10, max_retries=5, retry_delay=4)
148
+ >>> get_public_ipv6(api=IPv6API.IP_SB, geo=True, timeout=10, max_retries=5, retry_delay=4, backoff_factor=1.2)
147
149
  {'data': {'latitude': 51.2993, 'region': None, 'city': None, 'country_code': 'DE', 'api': 'ip.sb', 'longitude': 9.491, 'country': 'Germany', 'organization': 'Hetzner Online', 'timezone': 'Europe/Berlin', 'ip': 'xx:xx:xx:xx::xx'}, 'status': True}
148
150
  ```
149
151
 
@@ -164,7 +166,7 @@ Dynamic: summary
164
166
  ```console
165
167
  > ipspot --version
166
168
 
167
- 0.5
169
+ 0.7
168
170
  ```
169
171
 
170
172
  #### Info
@@ -172,18 +174,18 @@ Dynamic: summary
172
174
  ```console
173
175
  > ipspot --info
174
176
 
175
- ___ ____ ____ _
176
- |_ _|| _ \ / ___| _ __ ___ | |_
177
+ ___ ____ ____ _
178
+ |_ _|| _ \ / ___| _ __ ___ | |_
177
179
  | | | |_) |\___ \ | '_ \ / _ \ | __|
178
- | | | __/ ___) || |_) || (_) || |_
180
+ | | | __/ ___) || |_) || (_) || |_
179
181
  |___||_| |____/ | .__/ \___/ \__|
180
- |_|
182
+ |_|
181
183
 
182
- __ __ ___ ____
183
- \ \ / / _ / _ \ | ___|
184
- \ \ / / (_)| | | | |___ \
185
- \ V / _ | |_| | _ ___) |
186
- \_/ (_) \___/ (_)|____/
184
+ __ __ ___ _____
185
+ \ \ / / _ / _ \ |___ |
186
+ \ \ / / (_)| | | | / /
187
+ \ V / _ | |_| | _ / /
188
+ \_/ (_) \___/ (_) /_/
187
189
 
188
190
 
189
191
 
@@ -207,16 +209,31 @@ Private IP:
207
209
 
208
210
  Public IP and Location Info:
209
211
 
210
- API: ip-api.com
211
- City: Southampton
212
- Country: United Kingdom
213
- Country Code: GB
214
- IP: xx.xx.xx.xx
215
- Latitude: 50.9097
216
- Longitude: -1.4043
217
- Organization: N/A
218
- Region: England
219
- Timezone: Europe/London
212
+ IPv4:
213
+
214
+ API: ipinfo.io
215
+ City: Nuremberg
216
+ Country: Germany
217
+ Country Code: DE
218
+ IP: xx.xx.xx.xx
219
+ Latitude: 49.4527
220
+ Longitude: 11.0783
221
+ Organization: Hetzner Online GmbH
222
+ Region: Bavaria
223
+ Timezone: Europe/Berlin
224
+
225
+ IPv6:
226
+
227
+ API: ip.sb
228
+ City: N/A
229
+ Country: Germany
230
+ Country Code: DE
231
+ IP: xx:xx:xx:xx::xx
232
+ Latitude: 51.2993
233
+ Longitude: 9.491
234
+ Organization: Hetzner Online
235
+ Region: N/A
236
+ Timezone: Europe/Berlin
220
237
  ```
221
238
 
222
239
  #### IPv4 API
@@ -264,7 +281,7 @@ Public IP and Location Info:
264
281
 
265
282
  #### IPv6 API
266
283
 
267
- ℹ️ `ipv6-api` valid choices: [`auto-safe`, `auto`, `ip.sb`, `ident.me`, `tnedi.me`, `ipleak.net`, `my-ip.io`]
284
+ ℹ️ `ipv6-api` valid choices: [`auto-safe`, `auto`, `ip.sb`, `ident.me`, `tnedi.me`, `ipleak.net`, `my-ip.io`, `ifconfig.co`, `reallyfreegeoip.org`, `myip.la`, `freeipapi.com`]
268
285
 
269
286
  ℹ️ The default value: `auto-safe`
270
287
 
@@ -360,6 +377,31 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
360
377
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
361
378
 
362
379
  ## [Unreleased]
380
+ ## [0.7] - 2025-12-09
381
+ ### Added
382
+ - `--backoff-factor` argument
383
+ ### Changed
384
+ - CLI messages updated
385
+ - `Python 3.14` added to `test.yml`
386
+ - Internal functions default values removed
387
+ - `README.md` updated
388
+ - Test system modified
389
+ - `ipspot_info` function renamed to `_print_ipspot_info`
390
+ - `display_ip_info` function renamed to `_print_report`
391
+ ## [0.6] - 2025-11-18
392
+ ### Added
393
+ - `ForceIPHTTPAdapter` class
394
+ - `_get_json_force_ip` function
395
+ - Support [ifconfig.co](https://ifconfig.co/json) IPv6 API
396
+ - Support [reallyfreegeoip.org](https://reallyfreegeoip.org/json/) IPv6 API
397
+ - Support [myip.la](https://api.myip.la/en?json) IPv6 API
398
+ - Support [freeipapi.com](https://freeipapi.com/api/json) IPv6 API
399
+ ### Changed
400
+ - [freeipapi.com](https://freeipapi.com/api/json) IPv4 API bug fixed
401
+ - `README.md` updated
402
+ ### Removed
403
+ - `IPv4HTTPAdapter` class
404
+ - `_get_json_ipv4_forced` function
363
405
  ## [0.5] - 2025-10-17
364
406
  ### Added
365
407
  - `setup-warp` action
@@ -438,7 +480,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
438
480
  - `--no-geo` argument
439
481
  - Logo
440
482
 
441
- [Unreleased]: https://github.com/openscilab/ipspot/compare/v0.5...dev
483
+ [Unreleased]: https://github.com/openscilab/ipspot/compare/v0.7...dev
484
+ [0.7]: https://github.com/openscilab/ipspot/compare/v0.6...v0.7
485
+ [0.6]: https://github.com/openscilab/ipspot/compare/v0.5...v0.6
442
486
  [0.5]: https://github.com/openscilab/ipspot/compare/v0.4...v0.5
443
487
  [0.4]: https://github.com/openscilab/ipspot/compare/v0.3...v0.4
444
488
  [0.3]: https://github.com/openscilab/ipspot/compare/v0.2...v0.3
@@ -0,0 +1,14 @@
1
+ ipspot/__init__.py,sha256=B47uAOAidLkXps1A4zDig6NN2kB3HcdMC20UOrrEfB4,281
2
+ ipspot/__main__.py,sha256=xKHY_tc94SWktkIHV0O9YADAHPZvT8yXOFSm_L2yMno,105
3
+ ipspot/cli.py,sha256=Y48HA_s6SrTCtjn-iAFNMplAARLwBUx8zpxesG0hARE,4980
4
+ ipspot/ipv4.py,sha256=QGeVZWruziLwe0bxtvo7k0xrnY73-znsXtSsvELYdyE,21613
5
+ ipspot/ipv6.py,sha256=xkgKyTSP_3TTijeM0BftiKL1KlIqMtRucb16-2QrkzA,14081
6
+ ipspot/params.py,sha256=u8eEDVB40Fo52DRhD7-mPg30U62Brtc2aGi9ZhqYuC8,2142
7
+ ipspot/utils.py,sha256=W3HJ61_KVtdg4gWr4m9xXblQc_E30rJoF1aKk36Zqzg,4591
8
+ ipspot-0.7.dist-info/licenses/AUTHORS.md,sha256=5ZvxP1KnuVkurB4psQDSqnqiAyn44q1aeiXUsnYgps4,411
9
+ ipspot-0.7.dist-info/licenses/LICENSE,sha256=0aOd4wzZRoSH_35UZXRHS7alPFTtuFEBJrajLuonEIw,1067
10
+ ipspot-0.7.dist-info/METADATA,sha256=P0uWs_6IZgVWPXJd53rsfvrepgJwLUq7Y8v_FNTDzB0,15395
11
+ ipspot-0.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
+ ipspot-0.7.dist-info/entry_points.txt,sha256=DJVLepYr8H3UcvWekU5Jy-tcr_xmWrphzgWGNOd3hlk,43
13
+ ipspot-0.7.dist-info/top_level.txt,sha256=v0WgE1z2iCy_bXU53fVcllwHLTvGNBIvq8u3KPC2_Sc,7
14
+ ipspot-0.7.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- ipspot/__init__.py,sha256=B47uAOAidLkXps1A4zDig6NN2kB3HcdMC20UOrrEfB4,281
2
- ipspot/__main__.py,sha256=xKHY_tc94SWktkIHV0O9YADAHPZvT8yXOFSm_L2yMno,105
3
- ipspot/cli.py,sha256=MdyMh-9miuT8zQgklVrvsimaXnqD17sviWRJbUWlk58,4637
4
- ipspot/ipv4.py,sha256=-HGv-yAsFI56I06qNFDquVVGP9ZhfWwih1Vs3JtemZQ,23825
5
- ipspot/ipv6.py,sha256=svGgydUUSoXeuT2hLgi5CDDy3n2PtV-b6uDvP5t_iKI,8754
6
- ipspot/params.py,sha256=PVnz3F1jB5aIle_fScGoF-0MfREhMxcTCXGEXmimUQs,1741
7
- ipspot/utils.py,sha256=YtkyWGljpFc72rSGljsARjlVrF9hkE7Gl78_5uex1yI,1870
8
- ipspot-0.5.dist-info/licenses/AUTHORS.md,sha256=5ZvxP1KnuVkurB4psQDSqnqiAyn44q1aeiXUsnYgps4,411
9
- ipspot-0.5.dist-info/licenses/LICENSE,sha256=0aOd4wzZRoSH_35UZXRHS7alPFTtuFEBJrajLuonEIw,1067
10
- ipspot-0.5.dist-info/METADATA,sha256=Sbzs3OQ0QZH22X8bNrcLxWbYb1zKBtKwbPf9AVAEon0,13824
11
- ipspot-0.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
- ipspot-0.5.dist-info/entry_points.txt,sha256=DJVLepYr8H3UcvWekU5Jy-tcr_xmWrphzgWGNOd3hlk,43
13
- ipspot-0.5.dist-info/top_level.txt,sha256=v0WgE1z2iCy_bXU53fVcllwHLTvGNBIvq8u3KPC2_Sc,7
14
- ipspot-0.5.dist-info/RECORD,,
File without changes