ipspot 0.2__tar.gz → 0.3__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.
@@ -5,6 +5,23 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5
5
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
+ ## [0.3] - 2025-05-19
9
+ ### Added
10
+ - `is_ipv4` function
11
+ - `is_loopback` function
12
+ - `IPv4HTTPAdapter` class
13
+ - Support [ident.me](https://ident.me/json)
14
+ - Support [tnedi.me](https://tnedi.me/json)
15
+ ### Changed
16
+ - `get_private_ipv4` function modified
17
+ - `get_public_ipv4` function modified
18
+ - `_ipsb_ipv4` function modified
19
+ - `_ipapi_ipv4` function modified
20
+ - `_ipinfo_ipv4` function modified
21
+ - `functions.py` renamed to `utils.py`
22
+ - CLI functions moved to `cli.py`
23
+ - IPv4 functions moved to `ipv4.py`
24
+ - Test system modified
8
25
  ## [0.2] - 2025-05-04
9
26
  ### Added
10
27
  - Support [ip.sb](https://api.ip.sb/geoip)
@@ -24,7 +41,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
24
41
  - `--no-geo` argument
25
42
  - Logo
26
43
 
27
- [Unreleased]: https://github.com/openscilab/ipspot/compare/v0.2...dev
44
+ [Unreleased]: https://github.com/openscilab/ipspot/compare/v0.3...dev
45
+ [0.3]: https://github.com/openscilab/ipspot/compare/v0.2...v0.3
28
46
  [0.2]: https://github.com/openscilab/ipspot/compare/v0.1...v0.2
29
47
  [0.1]: https://github.com/openscilab/ipspot/compare/3216fb7...v0.1
30
48
 
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ipspot
3
- Version: 0.2
3
+ Version: 0.3
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.2
6
+ Download-URL: https://github.com/openscilab/ipspot/tarball/v0.3
7
7
  Author: IPSpot Development Team
8
8
  Author-email: ipspot@openscilab.com
9
9
  License: MIT
@@ -102,13 +102,13 @@ Dynamic: summary
102
102
  ## Installation
103
103
 
104
104
  ### Source Code
105
- - Download [Version 0.2](https://github.com/openscilab/ipspot/archive/v0.2.zip) or [Latest Source](https://github.com/openscilab/ipspot/archive/dev.zip)
105
+ - Download [Version 0.3](https://github.com/openscilab/ipspot/archive/v0.3.zip) or [Latest Source](https://github.com/openscilab/ipspot/archive/dev.zip)
106
106
  - `pip install .`
107
107
 
108
108
  ### PyPI
109
109
 
110
110
  - Check [Python Packaging User Guide](https://packaging.python.org/installing/)
111
- - `pip install ipspot==0.2`
111
+ - `pip install ipspot==0.3`
112
112
 
113
113
 
114
114
  ## Usage
@@ -142,7 +142,7 @@ Dynamic: summary
142
142
  ```console
143
143
  > ipspot --version
144
144
 
145
- 0.2
145
+ 0.3
146
146
  ```
147
147
 
148
148
  #### Info
@@ -157,11 +157,11 @@ Dynamic: summary
157
157
  |___||_| |____/ | .__/ \___/ \__|
158
158
  |_|
159
159
 
160
- __ __ ___ ____
161
- \ \ / / _ / _ \ |___ \
162
- \ \ / / (_)| | | | __) |
163
- \ V / _ | |_| | _ / __/
164
- \_/ (_) \___/ (_)|_____|
160
+ __ __ ___ _____
161
+ \ \ / / _ / _ \ |___ /
162
+ \ \ / / (_)| | | | |_ \
163
+ \ V / _ | |_| | _ ___) |
164
+ \_/ (_) \___/ (_)|____/
165
165
 
166
166
 
167
167
 
@@ -197,7 +197,7 @@ Public IP and Location Info:
197
197
 
198
198
  #### IPv4 API
199
199
 
200
- ℹ️ `ipv4-api` valid choices: [`auto`, `ipapi`, `ipinfo`, `ipsb`]
200
+ ℹ️ `ipv4-api` valid choices: [`auto`, `ipapi`, `ipinfo`, `ipsb`, `identme`, `tnedime`]
201
201
 
202
202
  ℹ️ The default value: `auto`
203
203
 
@@ -267,6 +267,23 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
267
267
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
268
268
 
269
269
  ## [Unreleased]
270
+ ## [0.3] - 2025-05-19
271
+ ### Added
272
+ - `is_ipv4` function
273
+ - `is_loopback` function
274
+ - `IPv4HTTPAdapter` class
275
+ - Support [ident.me](https://ident.me/json)
276
+ - Support [tnedi.me](https://tnedi.me/json)
277
+ ### Changed
278
+ - `get_private_ipv4` function modified
279
+ - `get_public_ipv4` function modified
280
+ - `_ipsb_ipv4` function modified
281
+ - `_ipapi_ipv4` function modified
282
+ - `_ipinfo_ipv4` function modified
283
+ - `functions.py` renamed to `utils.py`
284
+ - CLI functions moved to `cli.py`
285
+ - IPv4 functions moved to `ipv4.py`
286
+ - Test system modified
270
287
  ## [0.2] - 2025-05-04
271
288
  ### Added
272
289
  - Support [ip.sb](https://api.ip.sb/geoip)
@@ -286,7 +303,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
286
303
  - `--no-geo` argument
287
304
  - Logo
288
305
 
289
- [Unreleased]: https://github.com/openscilab/ipspot/compare/v0.2...dev
306
+ [Unreleased]: https://github.com/openscilab/ipspot/compare/v0.3...dev
307
+ [0.3]: https://github.com/openscilab/ipspot/compare/v0.2...v0.3
290
308
  [0.2]: https://github.com/openscilab/ipspot/compare/v0.1...v0.2
291
309
  [0.1]: https://github.com/openscilab/ipspot/compare/3216fb7...v0.1
292
310
 
@@ -52,13 +52,13 @@
52
52
  ## Installation
53
53
 
54
54
  ### Source Code
55
- - Download [Version 0.2](https://github.com/openscilab/ipspot/archive/v0.2.zip) or [Latest Source](https://github.com/openscilab/ipspot/archive/dev.zip)
55
+ - Download [Version 0.3](https://github.com/openscilab/ipspot/archive/v0.3.zip) or [Latest Source](https://github.com/openscilab/ipspot/archive/dev.zip)
56
56
  - `pip install .`
57
57
 
58
58
  ### PyPI
59
59
 
60
60
  - Check [Python Packaging User Guide](https://packaging.python.org/installing/)
61
- - `pip install ipspot==0.2`
61
+ - `pip install ipspot==0.3`
62
62
 
63
63
 
64
64
  ## Usage
@@ -92,7 +92,7 @@
92
92
  ```console
93
93
  > ipspot --version
94
94
 
95
- 0.2
95
+ 0.3
96
96
  ```
97
97
 
98
98
  #### Info
@@ -107,11 +107,11 @@
107
107
  |___||_| |____/ | .__/ \___/ \__|
108
108
  |_|
109
109
 
110
- __ __ ___ ____
111
- \ \ / / _ / _ \ |___ \
112
- \ \ / / (_)| | | | __) |
113
- \ V / _ | |_| | _ / __/
114
- \_/ (_) \___/ (_)|_____|
110
+ __ __ ___ _____
111
+ \ \ / / _ / _ \ |___ /
112
+ \ \ / / (_)| | | | |_ \
113
+ \ V / _ | |_| | _ ___) |
114
+ \_/ (_) \___/ (_)|____/
115
115
 
116
116
 
117
117
 
@@ -147,7 +147,7 @@ Public IP and Location Info:
147
147
 
148
148
  #### IPv4 API
149
149
 
150
- ℹ️ `ipv4-api` valid choices: [`auto`, `ipapi`, `ipinfo`, `ipsb`]
150
+ ℹ️ `ipv4-api` valid choices: [`auto`, `ipapi`, `ipinfo`, `ipsb`, `identme`, `tnedime`]
151
151
 
152
152
  ℹ️ The default value: `auto`
153
153
 
@@ -4,8 +4,8 @@
4
4
 
5
5
  | Version | Supported |
6
6
  | ------------- | ------------------ |
7
- | 0.2 | :white_check_mark: |
8
- | < 0.2 | :x: |
7
+ | 0.3 | :white_check_mark: |
8
+ | < 0.3 | :x: |
9
9
 
10
10
  ## Reporting a Vulnerability
11
11
 
@@ -1,5 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """ipspot modules."""
3
3
  from .params import IPSPOT_VERSION, IPv4API
4
- from .functions import get_private_ipv4, get_public_ipv4
4
+ from .ipv4 import get_private_ipv4, get_public_ipv4, is_ipv4
5
+ from .utils import is_loopback
5
6
  __version__ = IPSPOT_VERSION
@@ -1,6 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """ipspot main."""
3
- from .functions import main
3
+ from .cli import main
4
4
 
5
5
 
6
6
  if __name__ == "__main__":
@@ -0,0 +1,73 @@
1
+ # -*- coding: utf-8 -*-
2
+ """ipspot CLI."""
3
+ import argparse
4
+ from typing import Union, Tuple
5
+ from art import tprint
6
+ from .ipv4 import get_public_ipv4, get_private_ipv4
7
+ from .utils import filter_parameter
8
+ from .params import IPv4API, PARAMETERS_NAME_MAP
9
+ from .params import IPSPOT_OVERVIEW, IPSPOT_REPO, IPSPOT_VERSION
10
+
11
+
12
+ def ipspot_info() -> None: # pragma: no cover
13
+ """Print ipspot details."""
14
+ tprint("IPSpot")
15
+ tprint("V:" + IPSPOT_VERSION)
16
+ print(IPSPOT_OVERVIEW)
17
+ print("Repo : " + IPSPOT_REPO)
18
+
19
+
20
+ def display_ip_info(ipv4_api: IPv4API = IPv4API.AUTO, geo: bool=False,
21
+ timeout: Union[float, Tuple[float, float]]=5) -> None: # pragma: no cover
22
+ """
23
+ Print collected IP and location data.
24
+
25
+ :param ipv4_api: public IPv4 API
26
+ :param geo: geolocation flag
27
+ :param timeout: timeout value for API
28
+ """
29
+ private_result = get_private_ipv4()
30
+ print("Private IP:\n")
31
+ print(" IP: {private_result[data][ip]}".format(private_result=private_result) if private_result["status"]
32
+ else " Error: {private_result[error]}".format(private_result=private_result))
33
+
34
+ public_title = "\nPublic IP"
35
+ if geo:
36
+ public_title += " and Location Info"
37
+ public_title += ":\n"
38
+ print(public_title)
39
+ public_result = get_public_ipv4(ipv4_api, geo=geo, timeout=timeout)
40
+ if public_result["status"]:
41
+ for name, parameter in sorted(public_result["data"].items()):
42
+ print(
43
+ " {name}: {parameter}".format(
44
+ name=PARAMETERS_NAME_MAP[name],
45
+ parameter=filter_parameter(parameter)))
46
+ else:
47
+ print(" Error: {public_result[error]}".format(public_result=public_result))
48
+
49
+
50
+ def main() -> None: # pragma: no cover
51
+ """CLI main function."""
52
+ parser = argparse.ArgumentParser()
53
+ parser.add_argument(
54
+ '--ipv4-api',
55
+ help='public IPv4 API',
56
+ type=str.lower,
57
+ choices=[
58
+ x.value for x in IPv4API],
59
+ default=IPv4API.AUTO.value)
60
+ parser.add_argument('--info', help='info', nargs="?", const=1)
61
+ parser.add_argument('--version', help='version', nargs="?", const=1)
62
+ parser.add_argument('--no-geo', help='no geolocation data', nargs="?", const=1, default=False)
63
+ parser.add_argument('--timeout', help='timeout for the API request', type=float, default=5.0)
64
+
65
+ args = parser.parse_args()
66
+ if args.version:
67
+ print(IPSPOT_VERSION)
68
+ elif args.info:
69
+ ipspot_info()
70
+ else:
71
+ ipv4_api = IPv4API(args.ipv4_api)
72
+ geo = not args.no_geo
73
+ display_ip_info(ipv4_api=ipv4_api, geo=geo, timeout=args.timeout)
@@ -0,0 +1,273 @@
1
+ # -*- coding: utf-8 -*-
2
+ """ipspot ipv4 functions."""
3
+ import ipaddress
4
+ import socket
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
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 is_ipv4(ip: str) -> bool:
57
+ """
58
+ Check if the given input is a valid IPv4 address.
59
+
60
+ :param ip: input IP
61
+ """
62
+ if not isinstance(ip, str):
63
+ return False
64
+ try:
65
+ _ = ipaddress.IPv4Address(ip)
66
+ return True
67
+ except Exception:
68
+ return False
69
+
70
+
71
+ def get_private_ipv4() -> Dict[str, Union[bool, Dict[str, str], str]]:
72
+ """Retrieve the private IPv4 address."""
73
+ try:
74
+ with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
75
+ s.connect(('192.168.1.1', 1))
76
+ private_ip = s.getsockname()[0]
77
+ if is_ipv4(private_ip) and not is_loopback(private_ip):
78
+ return {"status": True, "data": {"ip": private_ip}}
79
+ return {"status": False, "error": "Could not identify a non-loopback IPv4 address for this system."}
80
+ except Exception as e:
81
+ return {"status": False, "error": str(e)}
82
+
83
+
84
+ def _ipsb_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
85
+ =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
86
+ """
87
+ Get public IP and geolocation using ip.sb.
88
+
89
+ :param geo: geolocation flag
90
+ :param timeout: timeout value for API
91
+ """
92
+ try:
93
+ with requests.Session() as session:
94
+ response = session.get("https://api-ipv4.ip.sb/geoip", headers=REQUEST_HEADERS, timeout=timeout)
95
+ response.raise_for_status()
96
+ data = response.json()
97
+ result = {"status": True, "data": {"ip": data.get("ip"), "api": "ip.sb"}}
98
+ if geo:
99
+ geo_data = {
100
+ "city": data.get("city"),
101
+ "region": data.get("region"),
102
+ "country": data.get("country"),
103
+ "country_code": data.get("country_code"),
104
+ "latitude": data.get("latitude"),
105
+ "longitude": data.get("longitude"),
106
+ "organization": data.get("organization"),
107
+ "timezone": data.get("timezone")
108
+ }
109
+ result["data"].update(geo_data)
110
+ return result
111
+ except Exception as e:
112
+ return {"status": False, "error": str(e)}
113
+
114
+
115
+ def _ipapi_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
116
+ =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
117
+ """
118
+ Get public IP and geolocation using ip-api.com.
119
+
120
+ :param geo: geolocation flag
121
+ :param timeout: timeout value for API
122
+ """
123
+ try:
124
+ with requests.Session() as session:
125
+ session.mount("http://", IPv4HTTPAdapter())
126
+ session.mount("https://", IPv4HTTPAdapter())
127
+ response = session.get("http://ip-api.com/json/", headers=REQUEST_HEADERS, timeout=timeout)
128
+ response.raise_for_status()
129
+ data = response.json()
130
+ if data.get("status") != "success":
131
+ return {"status": False, "error": "ip-api lookup failed"}
132
+ result = {"status": True, "data": {"ip": data.get("query"), "api": "ip-api.com"}}
133
+ if geo:
134
+ geo_data = {
135
+ "city": data.get("city"),
136
+ "region": data.get("regionName"),
137
+ "country": data.get("country"),
138
+ "country_code": data.get("countryCode"),
139
+ "latitude": data.get("lat"),
140
+ "longitude": data.get("lon"),
141
+ "organization": data.get("org"),
142
+ "timezone": data.get("timezone")
143
+ }
144
+ result["data"].update(geo_data)
145
+ return result
146
+ except Exception as e:
147
+ return {"status": False, "error": str(e)}
148
+
149
+
150
+ def _ipinfo_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
151
+ =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
152
+ """
153
+ Get public IP and geolocation using ipinfo.io.
154
+
155
+ :param geo: geolocation flag
156
+ :param timeout: timeout value for API
157
+ """
158
+ try:
159
+ with requests.Session() as session:
160
+ session.mount("http://", IPv4HTTPAdapter())
161
+ session.mount("https://", IPv4HTTPAdapter())
162
+ response = session.get("https://ipinfo.io/json", headers=REQUEST_HEADERS, timeout=timeout)
163
+ response.raise_for_status()
164
+ data = response.json()
165
+ result = {"status": True, "data": {"ip": data.get("ip"), "api": "ipinfo.io"}}
166
+ if geo:
167
+ loc = data.get("loc", "").split(",")
168
+ geo_data = {
169
+ "city": data.get("city"),
170
+ "region": data.get("region"),
171
+ "country": None,
172
+ "country_code": data.get("country"),
173
+ "latitude": float(loc[0]) if len(loc) == 2 else None,
174
+ "longitude": float(loc[1]) if len(loc) == 2 else None,
175
+ "organization": data.get("org"),
176
+ "timezone": data.get("timezone")
177
+ }
178
+ result["data"].update(geo_data)
179
+ return result
180
+ except Exception as e:
181
+ return {"status": False, "error": str(e)}
182
+
183
+
184
+ def _ident_me_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
185
+ =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
186
+ """
187
+ Get public IP and geolocation using ident.me.
188
+
189
+ :param geo: geolocation flag
190
+ :param timeout: timeout value for API
191
+ """
192
+ try:
193
+ with requests.Session() as session:
194
+ response = session.get("https://4.ident.me/json", headers=REQUEST_HEADERS, timeout=timeout)
195
+ response.raise_for_status()
196
+ data = response.json()
197
+ result = {"status": True, "data": {"ip": data.get("ip"), "api": "ident.me"}}
198
+ if geo:
199
+ geo_data = {
200
+ "city": data.get("city"),
201
+ "region": None,
202
+ "country": data.get("country"),
203
+ "country_code": data.get("cc"),
204
+ "latitude": data.get("latitude"),
205
+ "longitude": data.get("longitude"),
206
+ "organization": data.get("aso"),
207
+ "timezone": data.get("tz")
208
+ }
209
+ result["data"].update(geo_data)
210
+ return result
211
+ except Exception as e:
212
+ return {"status": False, "error": str(e)}
213
+
214
+
215
+ def _tnedime_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
216
+ =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
217
+ """
218
+ Get public IP and geolocation using tnedi.me.
219
+
220
+ :param geo: geolocation flag
221
+ :param timeout: timeout value for API
222
+ """
223
+ try:
224
+ with requests.Session() as session:
225
+ response = session.get("https://4.tnedi.me/json", headers=REQUEST_HEADERS, timeout=timeout)
226
+ response.raise_for_status()
227
+ data = response.json()
228
+ result = {"status": True, "data": {"ip": data.get("ip"), "api": "tnedi.me"}}
229
+ if geo:
230
+ geo_data = {
231
+ "city": data.get("city"),
232
+ "region": None,
233
+ "country": data.get("country"),
234
+ "country_code": data.get("cc"),
235
+ "latitude": data.get("latitude"),
236
+ "longitude": data.get("longitude"),
237
+ "organization": data.get("aso"),
238
+ "timezone": data.get("tz")
239
+ }
240
+ result["data"].update(geo_data)
241
+ return result
242
+ except Exception as e:
243
+ return {"status": False, "error": str(e)}
244
+
245
+
246
+ def get_public_ipv4(api: IPv4API=IPv4API.AUTO, geo: bool=False,
247
+ timeout: Union[float, Tuple[float, float]]=5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
248
+ """
249
+ Get public IPv4 and geolocation info based on the selected API.
250
+
251
+ :param api: public IPv4 API
252
+ :param geo: geolocation flag
253
+ :param timeout: timeout value for API
254
+ """
255
+ api_map = {
256
+ IPv4API.IDENTME: _ident_me_ipv4,
257
+ IPv4API.TNEDIME: _tnedime_ipv4,
258
+ IPv4API.IPSB: _ipsb_ipv4,
259
+ IPv4API.IPAPI: _ipapi_ipv4,
260
+ IPv4API.IPINFO: _ipinfo_ipv4,
261
+ }
262
+
263
+ if api == IPv4API.AUTO:
264
+ for _, func in api_map.items():
265
+ result = func(geo=geo, timeout=timeout)
266
+ if result["status"]:
267
+ return result
268
+ return {"status": False, "error": "All attempts failed."}
269
+ else:
270
+ func = api_map.get(api)
271
+ if func:
272
+ return func(geo=geo, timeout=timeout)
273
+ return {"status": False, "error": "Unsupported API: {api}".format(api=api)}
@@ -2,7 +2,7 @@
2
2
  """ipspot params."""
3
3
  from enum import Enum
4
4
 
5
- IPSPOT_VERSION = "0.2"
5
+ IPSPOT_VERSION = "0.3"
6
6
 
7
7
  IPSPOT_OVERVIEW = '''
8
8
  IPSpot is a Python library for retrieving the current system's IP address and location information.
@@ -25,6 +25,8 @@ class IPv4API(Enum):
25
25
  IPAPI = "ipapi"
26
26
  IPINFO = "ipinfo"
27
27
  IPSB = "ipsb"
28
+ IDENTME = "identme"
29
+ TNEDIME = "tnedime"
28
30
 
29
31
 
30
32
  PARAMETERS_NAME_MAP = {
@@ -0,0 +1,30 @@
1
+ # -*- coding: utf-8 -*-
2
+ """ipspot utils."""
3
+ import ipaddress
4
+ from typing import Any
5
+
6
+
7
+ def is_loopback(ip: str) -> bool:
8
+ """
9
+ Check if the given input IP is a loopback address.
10
+
11
+ :param ip: input IP
12
+ """
13
+ try:
14
+ ip_object = ipaddress.ip_address(ip)
15
+ return ip_object.is_loopback
16
+ except Exception:
17
+ return False
18
+
19
+
20
+ def filter_parameter(parameter: Any) -> Any:
21
+ """
22
+ Filter input parameter.
23
+
24
+ :param parameter: input parameter
25
+ """
26
+ if parameter is None:
27
+ return "N/A"
28
+ if isinstance(parameter, str) and len(parameter.strip()) == 0:
29
+ return "N/A"
30
+ return parameter
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ipspot
3
- Version: 0.2
3
+ Version: 0.3
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.2
6
+ Download-URL: https://github.com/openscilab/ipspot/tarball/v0.3
7
7
  Author: IPSpot Development Team
8
8
  Author-email: ipspot@openscilab.com
9
9
  License: MIT
@@ -102,13 +102,13 @@ Dynamic: summary
102
102
  ## Installation
103
103
 
104
104
  ### Source Code
105
- - Download [Version 0.2](https://github.com/openscilab/ipspot/archive/v0.2.zip) or [Latest Source](https://github.com/openscilab/ipspot/archive/dev.zip)
105
+ - Download [Version 0.3](https://github.com/openscilab/ipspot/archive/v0.3.zip) or [Latest Source](https://github.com/openscilab/ipspot/archive/dev.zip)
106
106
  - `pip install .`
107
107
 
108
108
  ### PyPI
109
109
 
110
110
  - Check [Python Packaging User Guide](https://packaging.python.org/installing/)
111
- - `pip install ipspot==0.2`
111
+ - `pip install ipspot==0.3`
112
112
 
113
113
 
114
114
  ## Usage
@@ -142,7 +142,7 @@ Dynamic: summary
142
142
  ```console
143
143
  > ipspot --version
144
144
 
145
- 0.2
145
+ 0.3
146
146
  ```
147
147
 
148
148
  #### Info
@@ -157,11 +157,11 @@ Dynamic: summary
157
157
  |___||_| |____/ | .__/ \___/ \__|
158
158
  |_|
159
159
 
160
- __ __ ___ ____
161
- \ \ / / _ / _ \ |___ \
162
- \ \ / / (_)| | | | __) |
163
- \ V / _ | |_| | _ / __/
164
- \_/ (_) \___/ (_)|_____|
160
+ __ __ ___ _____
161
+ \ \ / / _ / _ \ |___ /
162
+ \ \ / / (_)| | | | |_ \
163
+ \ V / _ | |_| | _ ___) |
164
+ \_/ (_) \___/ (_)|____/
165
165
 
166
166
 
167
167
 
@@ -197,7 +197,7 @@ Public IP and Location Info:
197
197
 
198
198
  #### IPv4 API
199
199
 
200
- ℹ️ `ipv4-api` valid choices: [`auto`, `ipapi`, `ipinfo`, `ipsb`]
200
+ ℹ️ `ipv4-api` valid choices: [`auto`, `ipapi`, `ipinfo`, `ipsb`, `identme`, `tnedime`]
201
201
 
202
202
  ℹ️ The default value: `auto`
203
203
 
@@ -267,6 +267,23 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
267
267
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
268
268
 
269
269
  ## [Unreleased]
270
+ ## [0.3] - 2025-05-19
271
+ ### Added
272
+ - `is_ipv4` function
273
+ - `is_loopback` function
274
+ - `IPv4HTTPAdapter` class
275
+ - Support [ident.me](https://ident.me/json)
276
+ - Support [tnedi.me](https://tnedi.me/json)
277
+ ### Changed
278
+ - `get_private_ipv4` function modified
279
+ - `get_public_ipv4` function modified
280
+ - `_ipsb_ipv4` function modified
281
+ - `_ipapi_ipv4` function modified
282
+ - `_ipinfo_ipv4` function modified
283
+ - `functions.py` renamed to `utils.py`
284
+ - CLI functions moved to `cli.py`
285
+ - IPv4 functions moved to `ipv4.py`
286
+ - Test system modified
270
287
  ## [0.2] - 2025-05-04
271
288
  ### Added
272
289
  - Support [ip.sb](https://api.ip.sb/geoip)
@@ -286,7 +303,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
286
303
  - `--no-geo` argument
287
304
  - Logo
288
305
 
289
- [Unreleased]: https://github.com/openscilab/ipspot/compare/v0.2...dev
306
+ [Unreleased]: https://github.com/openscilab/ipspot/compare/v0.3...dev
307
+ [0.3]: https://github.com/openscilab/ipspot/compare/v0.2...v0.3
290
308
  [0.2]: https://github.com/openscilab/ipspot/compare/v0.1...v0.2
291
309
  [0.1]: https://github.com/openscilab/ipspot/compare/3216fb7...v0.1
292
310
 
@@ -9,13 +9,15 @@ requirements.txt
9
9
  setup.py
10
10
  ipspot/__init__.py
11
11
  ipspot/__main__.py
12
- ipspot/functions.py
12
+ ipspot/cli.py
13
+ ipspot/ipv4.py
13
14
  ipspot/params.py
15
+ ipspot/utils.py
14
16
  ipspot.egg-info/PKG-INFO
15
17
  ipspot.egg-info/SOURCES.txt
16
18
  ipspot.egg-info/dependency_links.txt
17
19
  ipspot.egg-info/entry_points.txt
18
20
  ipspot.egg-info/requires.txt
19
21
  ipspot.egg-info/top_level.txt
20
- tests/test_functions.py
21
- tests/test_ipv4.py
22
+ tests/test_ipv4.py
23
+ tests/test_utils.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ipspot = ipspot.cli:main
@@ -32,7 +32,7 @@ def read_description() -> str:
32
32
  setup(
33
33
  name='ipspot',
34
34
  packages=['ipspot'],
35
- version='0.2',
35
+ version='0.3',
36
36
  description='IPSpot: A Python Tool to Fetch the System\'s IP Address',
37
37
  long_description=read_description(),
38
38
  long_description_content_type='text/markdown',
@@ -40,7 +40,7 @@ setup(
40
40
  author='IPSpot Development Team',
41
41
  author_email='ipspot@openscilab.com',
42
42
  url='https://github.com/openscilab/ipspot',
43
- download_url='https://github.com/openscilab/ipspot/tarball/v0.2',
43
+ download_url='https://github.com/openscilab/ipspot/tarball/v0.3',
44
44
  keywords="ip ipv4 geo geolocation network location ipspot cli",
45
45
  project_urls={
46
46
  'Source': 'https://github.com/openscilab/ipspot'
@@ -69,7 +69,7 @@ setup(
69
69
  license='MIT',
70
70
  entry_points={
71
71
  'console_scripts': [
72
- 'ipspot = ipspot.functions:main',
72
+ 'ipspot = ipspot.cli:main',
73
73
  ]
74
74
  }
75
75
  )
@@ -0,0 +1,202 @@
1
+ from unittest import mock
2
+ import requests
3
+ from ipspot import get_private_ipv4, is_ipv4
4
+ from ipspot import get_public_ipv4, IPv4API
5
+ from ipspot import is_loopback
6
+
7
+ TEST_CASE_NAME = "IPv4 tests"
8
+ DATA_ITEMS = {'country_code', 'latitude', 'longitude', 'api', 'country', 'timezone', 'organization', 'region', 'ip', 'city'}
9
+
10
+
11
+ def test_is_ipv4_1():
12
+ assert is_ipv4("192.168.0.1")
13
+
14
+
15
+ def test_is_ipv4_2():
16
+ assert is_ipv4("0.0.0.0")
17
+
18
+
19
+ def test_is_ipv4_3():
20
+ assert is_ipv4("255.255.255.255")
21
+
22
+
23
+ def test_is_ipv4_4():
24
+ assert not is_ipv4("256.0.0.1")
25
+
26
+
27
+ def test_is_ipv4_5():
28
+ assert not is_ipv4("abc.def.ghi.jkl")
29
+
30
+
31
+ def test_is_ipv4_6():
32
+ assert not is_ipv4(123)
33
+
34
+
35
+ def test_is_ipv4_7():
36
+ assert not is_ipv4("2001:0db8:85a3:0000:0000:8a2e:0370:7334")
37
+
38
+
39
+ def test_is_loopback_1():
40
+ assert not is_loopback("192.168.0.1")
41
+
42
+
43
+ def test_is_loopback_2():
44
+ assert is_loopback("127.0.0.1")
45
+
46
+
47
+ def test_is_loopback_3():
48
+ assert is_loopback("127.255.255.255")
49
+
50
+
51
+ def test_is_loopback_4():
52
+ assert not is_loopback("abc.def.ghi.jkl")
53
+
54
+
55
+ def test_private_ipv4_success():
56
+ result = get_private_ipv4()
57
+ assert result["status"]
58
+ assert is_ipv4(result["data"]["ip"])
59
+ assert not is_loopback(result["data"]["ip"])
60
+
61
+
62
+ def test_get_private_ipv4_loopback():
63
+ mock_socket = mock.MagicMock()
64
+ mock_socket.__enter__.return_value.getsockname.return_value = ('127.0.0.1',)
65
+ with mock.patch('socket.socket', return_value=mock_socket):
66
+ result = get_private_ipv4()
67
+ assert not result["status"]
68
+ assert result["error"] == "Could not identify a non-loopback IPv4 address for this system."
69
+
70
+
71
+ def test_get_private_ipv4_exception():
72
+ with mock.patch('socket.socket', side_effect=Exception("Test error")):
73
+ result = get_private_ipv4()
74
+ assert not result["status"]
75
+ assert result["error"] == "Test error"
76
+
77
+
78
+ def test_public_ipv4_auto_success():
79
+ result = get_public_ipv4(api=IPv4API.AUTO, geo=True)
80
+ assert result["status"]
81
+ assert is_ipv4(result["data"]["ip"])
82
+ assert set(result["data"].keys()) == DATA_ITEMS
83
+
84
+
85
+ def test_public_ipv4_auto_timeout_error():
86
+ result = get_public_ipv4(api=IPv4API.AUTO, geo=True, timeout="5")
87
+ assert not result["status"]
88
+
89
+
90
+ def test_public_ipv4_auto_net_error():
91
+ with mock.patch.object(requests.Session, "get", side_effect=Exception("No Internet")):
92
+ result = get_public_ipv4(api=IPv4API.AUTO)
93
+ assert not result["status"]
94
+ assert result["error"] == "All attempts failed."
95
+
96
+
97
+ def test_public_ipv4_ipapi_success():
98
+ result = get_public_ipv4(api=IPv4API.IPAPI, geo=True)
99
+ assert result["status"]
100
+ assert is_ipv4(result["data"]["ip"])
101
+ assert set(result["data"].keys()) == DATA_ITEMS
102
+ assert result["data"]["api"] == "ip-api.com"
103
+
104
+
105
+ def test_public_ipv4_ipapi_timeout_error():
106
+ result = get_public_ipv4(api=IPv4API.IPAPI, geo=True, timeout="5")
107
+ assert not result["status"]
108
+
109
+
110
+ def test_public_ipv4_ipapi_net_error():
111
+ with mock.patch.object(requests.Session, "get", side_effect=Exception("No Internet")):
112
+ result = get_public_ipv4(api=IPv4API.IPAPI)
113
+ assert not result["status"]
114
+ assert result["error"] == "No Internet"
115
+
116
+
117
+ def test_public_ipv4_ipinfo_success():
118
+ result = get_public_ipv4(api=IPv4API.IPINFO, geo=True)
119
+ assert result["status"]
120
+ assert is_ipv4(result["data"]["ip"])
121
+ assert set(result["data"].keys()) == DATA_ITEMS
122
+ assert result["data"]["api"] == "ipinfo.io"
123
+
124
+
125
+ def test_public_ipv4_ipinfo_timeout_error():
126
+ result = get_public_ipv4(api=IPv4API.IPINFO, geo=True, timeout="5")
127
+ assert not result["status"]
128
+
129
+
130
+ def test_public_ipv4_ipinfo_net_error():
131
+ with mock.patch.object(requests.Session, "get", side_effect=Exception("No Internet")):
132
+ result = get_public_ipv4(api=IPv4API.IPINFO)
133
+ assert not result["status"]
134
+ assert result["error"] == "No Internet"
135
+
136
+
137
+ def test_public_ipv4_ipsb_success():
138
+ result = get_public_ipv4(api=IPv4API.IPSB, geo=True, timeout=30)
139
+ assert result["status"]
140
+ assert is_ipv4(result["data"]["ip"])
141
+ assert set(result["data"].keys()) == DATA_ITEMS
142
+ assert result["data"]["api"] == "ip.sb"
143
+
144
+
145
+ def test_public_ipv4_ipsb_timeout_error():
146
+ result = get_public_ipv4(api=IPv4API.IPSB, geo=True, timeout="5")
147
+ assert not result["status"]
148
+
149
+
150
+
151
+ def test_public_ipv4_ipsb_net_error():
152
+ with mock.patch.object(requests.Session, "get", side_effect=Exception("No Internet")):
153
+ result = get_public_ipv4(api=IPv4API.IPSB)
154
+ assert not result["status"]
155
+ assert result["error"] == "No Internet"
156
+
157
+
158
+ def test_public_ipv4_identme_success():
159
+ result = get_public_ipv4(api=IPv4API.IDENTME, geo=True)
160
+ assert result["status"]
161
+ assert is_ipv4(result["data"]["ip"])
162
+ assert set(result["data"].keys()) == DATA_ITEMS
163
+ assert result["data"]["api"] == "ident.me"
164
+
165
+
166
+ def test_public_ipv4_identme_timeout_error():
167
+ result = get_public_ipv4(api=IPv4API.IDENTME, geo=True, timeout="5")
168
+ assert not result["status"]
169
+
170
+
171
+ def test_public_ipv4_identme_net_error():
172
+ with mock.patch.object(requests.Session, "get", side_effect=Exception("No Internet")):
173
+ result = get_public_ipv4(api=IPv4API.IDENTME)
174
+ assert not result["status"]
175
+ assert result["error"] == "No Internet"
176
+
177
+
178
+ def test_public_ipv4_tnedime_success():
179
+ result = get_public_ipv4(api=IPv4API.TNEDIME, geo=True)
180
+ assert result["status"]
181
+ assert is_ipv4(result["data"]["ip"])
182
+ assert set(result["data"].keys()) == DATA_ITEMS
183
+ assert result["data"]["api"] == "tnedi.me"
184
+
185
+
186
+ def test_public_ipv4_tnedime_timeout_error():
187
+ result = get_public_ipv4(api=IPv4API.TNEDIME, geo=True, timeout="5")
188
+ assert not result["status"]
189
+
190
+
191
+ def test_public_ipv4_tnedime_net_error():
192
+ with mock.patch.object(requests.Session, "get", side_effect=Exception("No Internet")):
193
+ result = get_public_ipv4(api=IPv4API.TNEDIME)
194
+ assert not result["status"]
195
+ assert result["error"] == "No Internet"
196
+
197
+
198
+ def test_public_ipv4_api_error():
199
+ result = get_public_ipv4(api="api1", geo=True)
200
+ assert not result["status"]
201
+ assert result["error"] == "Unsupported API: api1"
202
+
@@ -1,6 +1,6 @@
1
- from ipspot.functions import filter_parameter
1
+ from ipspot.utils import filter_parameter
2
2
 
3
- TEST_CASE_NAME = "Functions tests"
3
+ TEST_CASE_NAME = "Utils tests"
4
4
 
5
5
 
6
6
  def test_filter_parameter1():
@@ -1,218 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """ipspot functions."""
3
- import argparse
4
- import socket
5
- from typing import Union, Dict, Tuple, Any
6
- import requests
7
- from art import tprint
8
- from .params import REQUEST_HEADERS, IPv4API, PARAMETERS_NAME_MAP
9
- from .params import IPSPOT_OVERVIEW, IPSPOT_REPO, IPSPOT_VERSION
10
-
11
-
12
- def ipspot_info() -> None: # pragma: no cover
13
- """Print ipspot details."""
14
- tprint("IPSpot")
15
- tprint("V:" + IPSPOT_VERSION)
16
- print(IPSPOT_OVERVIEW)
17
- print("Repo : " + IPSPOT_REPO)
18
-
19
-
20
- def get_private_ipv4() -> Dict[str, Union[bool, Dict[str, str], str]]:
21
- """Retrieve the private IPv4 address."""
22
- try:
23
- hostname = socket.gethostname()
24
- private_ip = socket.gethostbyname(hostname)
25
- return {"status": True, "data": {"ip": private_ip}}
26
- except Exception as e:
27
- return {"status": False, "error": str(e)}
28
-
29
-
30
- def _ipsb_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
31
- =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
32
- """
33
- Get public IP and geolocation using ip.sb.
34
-
35
- :param geo: geolocation flag
36
- :param timeout: timeout value for API
37
- """
38
- try:
39
- response = requests.get("https://api.ip.sb/geoip", headers=REQUEST_HEADERS, timeout=timeout)
40
- response.raise_for_status()
41
- data = response.json()
42
- result = {"status": True, "data": {"ip": data.get("ip"), "api": "ip.sb"}}
43
- if geo:
44
- geo_data = {
45
- "city": data.get("city"),
46
- "region": data.get("region"),
47
- "country": data.get("country"),
48
- "country_code": data.get("country_code"),
49
- "latitude": data.get("latitude"),
50
- "longitude": data.get("longitude"),
51
- "organization": data.get("organization"),
52
- "timezone": data.get("timezone")
53
- }
54
- result["data"].update(geo_data)
55
- return result
56
- except Exception as e:
57
- return {"status": False, "error": str(e)}
58
-
59
-
60
- def _ipapi_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
61
- =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
62
- """
63
- Get public IP and geolocation using ip-api.com.
64
-
65
- :param geo: geolocation flag
66
- :param timeout: timeout value for API
67
- """
68
- try:
69
- response = requests.get("http://ip-api.com/json/", headers=REQUEST_HEADERS, timeout=timeout)
70
- response.raise_for_status()
71
- data = response.json()
72
-
73
- if data.get("status") != "success":
74
- return {"status": False, "error": "ip-api lookup failed"}
75
- result = {"status": True, "data": {"ip": data.get("query"), "api": "ip-api.com"}}
76
- if geo:
77
- geo_data = {
78
- "city": data.get("city"),
79
- "region": data.get("regionName"),
80
- "country": data.get("country"),
81
- "country_code": data.get("countryCode"),
82
- "latitude": data.get("lat"),
83
- "longitude": data.get("lon"),
84
- "organization": data.get("org"),
85
- "timezone": data.get("timezone")
86
- }
87
- result["data"].update(geo_data)
88
- return result
89
- except Exception as e:
90
- return {"status": False, "error": str(e)}
91
-
92
-
93
- def _ipinfo_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]]
94
- =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
95
- """
96
- Get public IP and geolocation using ipinfo.io.
97
-
98
- :param geo: geolocation flag
99
- :param timeout: timeout value for API
100
- """
101
- try:
102
- response = requests.get("https://ipinfo.io/json", headers=REQUEST_HEADERS, timeout=timeout)
103
- response.raise_for_status()
104
- data = response.json()
105
- result = {"status": True, "data": {"ip": data.get("ip"), "api": "ipinfo.io"}}
106
- if geo:
107
- loc = data.get("loc", "").split(",")
108
- geo_data = {
109
- "city": data.get("city"),
110
- "region": data.get("region"),
111
- "country": None,
112
- "country_code": data.get("country"),
113
- "latitude": float(loc[0]) if len(loc) == 2 else None,
114
- "longitude": float(loc[1]) if len(loc) == 2 else None,
115
- "organization": data.get("org"),
116
- "timezone": data.get("timezone")
117
- }
118
- result["data"].update(geo_data)
119
- return result
120
- except Exception as e:
121
- return {"status": False, "error": str(e)}
122
-
123
-
124
- def get_public_ipv4(api: IPv4API=IPv4API.AUTO, geo: bool=False,
125
- timeout: Union[float, Tuple[float, float]]=5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]:
126
- """
127
- Get public IPv4 and geolocation info based on the selected API.
128
-
129
- :param api: public IPv4 API
130
- :param geo: geolocation flag
131
- :param timeout: timeout value for API
132
- """
133
- api_map = {
134
- IPv4API.IPAPI: _ipapi_ipv4,
135
- IPv4API.IPINFO: _ipinfo_ipv4,
136
- IPv4API.IPSB: _ipsb_ipv4
137
- }
138
-
139
- if api == IPv4API.AUTO:
140
- for _, func in api_map.items():
141
- result = func(geo=geo, timeout=timeout)
142
- if result["status"]:
143
- return result
144
- return {"status": False, "error": "All attempts failed."}
145
- else:
146
- func = api_map.get(api)
147
- if func:
148
- return func(geo=geo, timeout=timeout)
149
- return {"status": False, "error": "Unsupported API: {api}".format(api=api)}
150
-
151
-
152
- def filter_parameter(parameter: Any) -> Any:
153
- """
154
- Filter input parameter.
155
-
156
- :param parameter: input parameter
157
- """
158
- if parameter is None:
159
- return "N/A"
160
- if isinstance(parameter, str) and len(parameter.strip()) == 0:
161
- return "N/A"
162
- return parameter
163
-
164
-
165
- def display_ip_info(ipv4_api: IPv4API = IPv4API.AUTO, geo: bool=False,
166
- timeout: Union[float, Tuple[float, float]]=5) -> None: # pragma: no cover
167
- """
168
- Print collected IP and location data.
169
-
170
- :param ipv4_api: public IPv4 API
171
- :param geo: geolocation flag
172
- :param timeout: timeout value for API
173
- """
174
- private_result = get_private_ipv4()
175
- print("Private IP:\n")
176
- print(" IP: {private_result[data][ip]}".format(private_result=private_result) if private_result["status"]
177
- else " Error: {private_result[error]}".format(private_result=private_result))
178
-
179
- public_title = "\nPublic IP"
180
- if geo:
181
- public_title += " and Location Info"
182
- public_title += ":\n"
183
- print(public_title)
184
- public_result = get_public_ipv4(ipv4_api, geo=geo, timeout=timeout)
185
- if public_result["status"]:
186
- for name, parameter in sorted(public_result["data"].items()):
187
- print(
188
- " {name}: {parameter}".format(
189
- name=PARAMETERS_NAME_MAP[name],
190
- parameter=filter_parameter(parameter)))
191
- else:
192
- print(" Error: {public_result[error]}".format(public_result=public_result))
193
-
194
-
195
- def main() -> None: # pragma: no cover
196
- """CLI main function."""
197
- parser = argparse.ArgumentParser()
198
- parser.add_argument(
199
- '--ipv4-api',
200
- help='public IPv4 API',
201
- type=str.lower,
202
- choices=[
203
- x.value for x in IPv4API],
204
- default=IPv4API.AUTO.value)
205
- parser.add_argument('--info', help='info', nargs="?", const=1)
206
- parser.add_argument('--version', help='version', nargs="?", const=1)
207
- parser.add_argument('--no-geo', help='no geolocation data', nargs="?", const=1, default=False)
208
- parser.add_argument('--timeout', help='timeout for the API request', type=float, default=5.0)
209
-
210
- args = parser.parse_args()
211
- if args.version:
212
- print(IPSPOT_VERSION)
213
- elif args.info:
214
- ipspot_info()
215
- else:
216
- ipv4_api = IPv4API(args.ipv4_api)
217
- geo = not args.no_geo
218
- display_ip_info(ipv4_api=ipv4_api, geo=geo, timeout=args.timeout)
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- ipspot = ipspot.functions:main
@@ -1,108 +0,0 @@
1
- import re
2
- from unittest import mock
3
- from ipspot import get_private_ipv4
4
- from ipspot import get_public_ipv4, IPv4API
5
-
6
- TEST_CASE_NAME = "IPv4 tests"
7
- IPV4_REGEX = re.compile(r'^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$')
8
- DATA_ITEMS = {'country_code', 'latitude', 'longitude', 'api', 'country', 'timezone', 'organization', 'region', 'ip', 'city'}
9
-
10
-
11
- def test_private_ipv4_success():
12
- result = get_private_ipv4()
13
- assert result["status"]
14
- assert IPV4_REGEX.match(result["data"]["ip"])
15
-
16
-
17
- def test_private_ipv4_error():
18
- with mock.patch("socket.gethostbyname", side_effect=Exception("Test error")):
19
- result = get_private_ipv4()
20
- assert not result["status"]
21
- assert result["error"] == "Test error"
22
-
23
-
24
- def test_public_ipv4_auto_success():
25
- result = get_public_ipv4(api=IPv4API.AUTO, geo=True)
26
- assert result["status"]
27
- assert IPV4_REGEX.match(result["data"]["ip"])
28
- assert set(result["data"].keys()) == DATA_ITEMS
29
-
30
-
31
- def test_public_ipv4_auto_timeout_error():
32
- result = get_public_ipv4(api=IPv4API.AUTO, geo=True, timeout="5")
33
- assert not result["status"]
34
-
35
-
36
- def test_public_ipv4_auto_net_error():
37
- with mock.patch("requests.get", side_effect=Exception("No Internet")):
38
- result = get_public_ipv4(api=IPv4API.AUTO)
39
- assert not result["status"]
40
- assert result["error"] == "All attempts failed."
41
-
42
-
43
- def test_public_ipv4_ipapi_success():
44
- result = get_public_ipv4(api=IPv4API.IPAPI, geo=True)
45
- assert result["status"]
46
- assert IPV4_REGEX.match(result["data"]["ip"])
47
- assert set(result["data"].keys()) == DATA_ITEMS
48
- assert result["data"]["api"] == "ip-api.com"
49
-
50
-
51
- def test_public_ipv4_ipapi_timeout_error():
52
- result = get_public_ipv4(api=IPv4API.IPAPI, geo=True, timeout="5")
53
- assert not result["status"]
54
-
55
-
56
- def test_public_ipv4_ipapi_net_error():
57
- with mock.patch("requests.get", side_effect=Exception("No Internet")):
58
- result = get_public_ipv4(api=IPv4API.IPAPI)
59
- assert not result["status"]
60
- assert result["error"] == "No Internet"
61
-
62
-
63
- def test_public_ipv4_ipinfo_success():
64
- result = get_public_ipv4(api=IPv4API.IPINFO, geo=True)
65
- assert result["status"]
66
- assert IPV4_REGEX.match(result["data"]["ip"])
67
- assert set(result["data"].keys()) == DATA_ITEMS
68
- assert result["data"]["api"] == "ipinfo.io"
69
-
70
-
71
- def test_public_ipv4_ipinfo_timeout_error():
72
- result = get_public_ipv4(api=IPv4API.IPINFO, geo=True, timeout="5")
73
- assert not result["status"]
74
-
75
-
76
- def test_public_ipv4_ipinfo_net_error():
77
- with mock.patch("requests.get", side_effect=Exception("No Internet")):
78
- result = get_public_ipv4(api=IPv4API.IPINFO)
79
- assert not result["status"]
80
- assert result["error"] == "No Internet"
81
-
82
-
83
- def test_public_ipv4_ipsb_success():
84
- result = get_public_ipv4(api=IPv4API.IPSB, geo=True)
85
- assert result["status"]
86
- assert IPV4_REGEX.match(result["data"]["ip"])
87
- assert set(result["data"].keys()) == DATA_ITEMS
88
- assert result["data"]["api"] == "ip.sb"
89
-
90
-
91
- def test_public_ipv4_ipsb_timeout_error():
92
- result = get_public_ipv4(api=IPv4API.IPSB, geo=True, timeout="5")
93
- assert not result["status"]
94
-
95
-
96
-
97
- def test_public_ipv4_ipsb_net_error():
98
- with mock.patch("requests.get", side_effect=Exception("No Internet")):
99
- result = get_public_ipv4(api=IPv4API.IPSB)
100
- assert not result["status"]
101
- assert result["error"] == "No Internet"
102
-
103
-
104
- def test_public_ipv4_api_error():
105
- result = get_public_ipv4(api="api1", geo=True)
106
- assert not result["status"]
107
- assert result["error"] == "Unsupported API: api1"
108
-
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes