From 39bb022abf117a16e0db41532eb01bc45b4c76d8 Mon Sep 17 00:00:00 2001 From: AHReccese Date: Wed, 17 Dec 2025 22:20:50 -0500 Subject: [PATCH 1/8] `CHANGELOG.md` updated --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b03154d..a771d5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Support [wtfismyip.com](https://wtfismyip.com/json) IPv6 API - Support [myip.wtf](https://myip.wtf/) IPv6 API - Support [myip.wtf](https://myip.wtf/) IPv4 API +- Support [db-ip.com](https://api.db-ip.com/v2/free/self) IPv4 API ### Changed - `README.md` updated ## [0.7] - 2025-12-09 From c937ef91b3b7c152c400c477ff4ba0001d8dd925 Mon Sep 17 00:00:00 2001 From: AHReccese Date: Wed, 17 Dec 2025 22:21:03 -0500 Subject: [PATCH 2/8] `README.md` updated --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c9ba7b5..89dc6a0 100644 --- a/README.md +++ b/README.md @@ -187,7 +187,7 @@ Public IP and Location Info: #### IPv4 API -ℹ️ `ipv4-api` valid choices: [`auto-safe`, `auto`, `ip-api.com`, `ipinfo.io`, `ip.sb`, `ident.me`, `tnedi.me`, `ipapi.co`, `ipleak.net`, `my-ip.io`, `ifconfig.co`, `reallyfreegeoip.org`, `freeipapi.com`, `myip.la`, `ipquery.io`, `ipwho.is`, `wtfismyip.com`, `myip.wtf`] +ℹ️ `ipv4-api` valid choices: [`auto-safe`, `auto`, `ip-api.com`, `ipinfo.io`, `ip.sb`, `ident.me`, `tnedi.me`, `ipapi.co`, `ipleak.net`, `my-ip.io`, `ifconfig.co`, `reallyfreegeoip.org`, `freeipapi.com`, `myip.la`, `ipquery.io`, `ipwho.is`, `wtfismyip.com`, `myip.wtf`, `db-ip.com`] ℹ️ The default value: `auto-safe` From 3f5db2401da62d607c5a1c87908e97b9f0e7d845 Mon Sep 17 00:00:00 2001 From: AHReccese Date: Wed, 17 Dec 2025 22:21:34 -0500 Subject: [PATCH 3/8] `_db_ip_com_ipv4` implemented --- ipspot/ipv4.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/ipspot/ipv4.py b/ipspot/ipv4.py index 8ce962d..8a045c7 100644 --- a/ipspot/ipv4.py +++ b/ipspot/ipv4.py @@ -494,6 +494,34 @@ def _myip_wtf_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]] return {"status": False, "error": str(e)} +def _db_ip_com_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]] + ) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]: + """ + Get public IP and geolocation using db-ip.com. + + :param geo: geolocation flag + :param timeout: timeout value for API + """ + try: + data = _get_json_standard(url="https://api.db-ip.com/v2/free/self", timeout=timeout) + result = {"status": True, "data": {"ip": data["ipAddress"], "api": "db-ip.com"}} + if geo: + geo_data = { + "city": data.get("city"), + "region": data.get("stateProv"), + "country": data.get("countryName"), + "country_code": data.get("countryCode"), + "latitude": None, # not provided by free API + "longitude": None, # not provided by free API + "organization": None, # not provided by free API + "timezone": None # not provided by free API + } + result["data"].update(geo_data) + return result + except Exception as e: + return {"status": False, "error": str(e)} + + IPV4_API_MAP = { IPv4API.IFCONFIG_CO: { "thread_safe": False, @@ -575,6 +603,11 @@ def _myip_wtf_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]] "geo": True, "function": _myip_wtf_ipv4 }, + IPv4API.DB_IP_COM: { + "thread_safe": True, + "geo": True, + "function": _db_ip_com_ipv4 + }, } From 4de72f4411d799c623878ba2c838092d627cbcf5 Mon Sep 17 00:00:00 2001 From: AHReccese Date: Wed, 17 Dec 2025 22:21:58 -0500 Subject: [PATCH 4/8] `DB_IP_COM` added to `IPv4API` --- ipspot/params.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ipspot/params.py b/ipspot/params.py index 0d675e0..9032ffe 100644 --- a/ipspot/params.py +++ b/ipspot/params.py @@ -39,6 +39,7 @@ class IPv4API(Enum): IPWHO_IS = "ipwho.is" WTFISMYIP_COM = "wtfismyip.com" MYIP_WTF = "myip.wtf" + DB_IP_COM = "db-ip.com" class IPv6API(Enum): From bd6b3df1935f4a4615675dc28d91dcb8c22fb367 Mon Sep 17 00:00:00 2001 From: AHReccese Date: Wed, 17 Dec 2025 22:22:28 -0500 Subject: [PATCH 5/8] add test for ipv4 for `db-ip.com` --- tests/test_ipv4_api.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_ipv4_api.py b/tests/test_ipv4_api.py index 3a47a52..014161d 100644 --- a/tests/test_ipv4_api.py +++ b/tests/test_ipv4_api.py @@ -145,3 +145,11 @@ def test_public_ipv4_myip_wtf_success(): assert is_ipv4(result["data"]["ip"]) assert set(result["data"].keys()) == DATA_ITEMS assert result["data"]["api"] == "myip.wtf" + + +def test_public_ipv4_db_ip_com_success(): + result = get_public_ipv4(api=IPv4API.DB_IP_COM, geo=True, timeout=40, max_retries=4, retry_delay=90, backoff_factor=1.1) + assert result["status"] + assert is_ipv4(result["data"]["ip"]) + assert set(result["data"].keys()) == DATA_ITEMS + assert result["data"]["api"] == "db-ip.com" \ No newline at end of file From 17182a0cdbc7cf5b987bd8568230b0b994f663c6 Mon Sep 17 00:00:00 2001 From: AHReccese Date: Wed, 17 Dec 2025 22:23:57 -0500 Subject: [PATCH 6/8] apply `autopep8.sh` [current implementation] --- ipspot/ipv4.py | 6 +++--- ipspot/ipv6.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ipspot/ipv4.py b/ipspot/ipv4.py index 8a045c7..4b6959d 100644 --- a/ipspot/ipv4.py +++ b/ipspot/ipv4.py @@ -467,7 +467,7 @@ def _wtfismyip_com_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]] def _myip_wtf_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]] - ) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]: + ) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]: """ Get public IP and geolocation using myip.wtf. @@ -611,8 +611,8 @@ def _db_ip_com_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]] } -def get_public_ipv4(api: IPv4API=IPv4API.AUTO_SAFE, geo: bool=False, - timeout: Union[float, Tuple[float, float]]=5, +def get_public_ipv4(api: IPv4API = IPv4API.AUTO_SAFE, geo: bool = False, + timeout: Union[float, Tuple[float, float]] = 5, max_retries: int = 0, retry_delay: float = 1.0, backoff_factor: float = 1.0) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]: diff --git a/ipspot/ipv6.py b/ipspot/ipv6.py index 7f8ab4c..fd7ab46 100644 --- a/ipspot/ipv6.py +++ b/ipspot/ipv6.py @@ -321,7 +321,7 @@ def _wtfismyip_com_ipv6(geo: bool, timeout: Union[float, Tuple[float, float]] def _myip_wtf_ipv6(geo: bool, timeout: Union[float, Tuple[float, float]] - ) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]: + ) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]: """ Get public IP and geolocation using myip.wtf. @@ -407,8 +407,8 @@ def _myip_wtf_ipv6(geo: bool, timeout: Union[float, Tuple[float, float]] } -def get_public_ipv6(api: IPv6API=IPv6API.AUTO_SAFE, geo: bool=False, - timeout: Union[float, Tuple[float, float]]=5, +def get_public_ipv6(api: IPv6API = IPv6API.AUTO_SAFE, geo: bool = False, + timeout: Union[float, Tuple[float, float]] = 5, max_retries: int = 0, retry_delay: float = 1.0, backoff_factor: float = 1.0) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]: From e6a648dc431d2c5dc2706ae8c493dd341afe04d7 Mon Sep 17 00:00:00 2001 From: AHReccese Date: Wed, 17 Dec 2025 22:31:34 -0500 Subject: [PATCH 7/8] update to force ipv4 --- ipspot/ipv4.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ipspot/ipv4.py b/ipspot/ipv4.py index 4b6959d..1fdf653 100644 --- a/ipspot/ipv4.py +++ b/ipspot/ipv4.py @@ -503,7 +503,7 @@ def _db_ip_com_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]] :param timeout: timeout value for API """ try: - data = _get_json_standard(url="https://api.db-ip.com/v2/free/self", timeout=timeout) + data = _get_json_force_ip(url="https://api.db-ip.com/v2/free/self", timeout=timeout, version="ipv4") result = {"status": True, "data": {"ip": data["ipAddress"], "api": "db-ip.com"}} if geo: geo_data = { @@ -604,7 +604,7 @@ def _db_ip_com_ipv4(geo: bool, timeout: Union[float, Tuple[float, float]] "function": _myip_wtf_ipv4 }, IPv4API.DB_IP_COM: { - "thread_safe": True, + "thread_safe": False, "geo": True, "function": _db_ip_com_ipv4 }, From 3b6d10878b42bb32834c59391c511114e8ddd0e7 Mon Sep 17 00:00:00 2001 From: AHReccese Date: Thu, 18 Dec 2025 10:32:02 -0500 Subject: [PATCH 8/8] add additional tests + unify spacing between test functions --- tests/test_ipv4_functions.py | 63 +++++++----------------------------- 1 file changed, 12 insertions(+), 51 deletions(-) diff --git a/tests/test_ipv4_functions.py b/tests/test_ipv4_functions.py index a5d8468..e526d3f 100644 --- a/tests/test_ipv4_functions.py +++ b/tests/test_ipv4_functions.py @@ -75,9 +75,6 @@ def test_get_private_ipv4_exception(): assert result["error"] == "Test error" - - - def test_public_ipv4_auto_timeout_error(): result = get_public_ipv4(api=IPv4API.AUTO, geo=True, timeout="5") assert not result["status"] @@ -90,9 +87,6 @@ def test_public_ipv4_auto_net_error(): assert result["error"] == "All attempts failed." - - - def test_public_ipv4_auto_safe_timeout_error(): result = get_public_ipv4(api=IPv4API.AUTO_SAFE, geo=True, timeout="5") assert not result["status"] @@ -105,9 +99,6 @@ def test_public_ipv4_auto_safe_net_error(): assert result["error"] == "All attempts failed." - - - def test_public_ipv4_ipapi_co_timeout_error(): result = get_public_ipv4(api=IPv4API.IPAPI_CO, geo=True, timeout="5") assert not result["status"] @@ -120,9 +111,6 @@ def test_public_ipv4_ipapi_co_net_error(): assert result["error"] == "No Internet" - - - def test_public_ipv4_ipleak_net_timeout_error(): result = get_public_ipv4(api=IPv4API.IPLEAK_NET, geo=True, timeout="5") assert not result["status"] @@ -135,9 +123,6 @@ def test_public_ipv4_ipleak_net_net_error(): assert result["error"] == "No Internet" - - - def test_public_ipv4_my_ip_io_timeout_error(): result = get_public_ipv4(api=IPv4API.MY_IP_IO, geo=True, timeout="5") assert not result["status"] @@ -150,9 +135,6 @@ def test_public_ipv4_my_ip_io_net_error(): assert result["error"] == "No Internet" - - - def test_public_ipv4_ifconfig_co_timeout_error(): result = get_public_ipv4(api=IPv4API.IFCONFIG_CO, geo=True, timeout="5") assert not result["status"] @@ -165,9 +147,6 @@ def test_public_ipv4_ifconfig_co_net_error(): assert result["error"] == "No Internet" - - - def test_public_ipv4_myip_la_timeout_error(): result = get_public_ipv4(api=IPv4API.MYIP_LA, geo=True, timeout="5") assert not result["status"] @@ -180,9 +159,6 @@ def test_public_ipv4_myip_la_net_error(): assert result["error"] == "No Internet" - - - def test_public_ipv4_ipquery_io_timeout_error(): result = get_public_ipv4(api=IPv4API.IPQUERY_IO, geo=True, timeout="5") assert not result["status"] @@ -195,9 +171,6 @@ def test_public_ipv4_ipquery_io_net_error(): assert result["error"] == "No Internet" - - - def test_public_ipv4_ipwho_is_timeout_error(): result = get_public_ipv4(api=IPv4API.IPWHO_IS, geo=True, timeout="5") assert not result["status"] @@ -210,9 +183,6 @@ def test_public_ipv4_ipwho_is_net_error(): assert result["error"] == "No Internet" - - - def test_public_ipv4_freeipapi_com_timeout_error(): result = get_public_ipv4(api=IPv4API.FREEIPAPI_COM, geo=True, timeout="5") assert not result["status"] @@ -225,9 +195,6 @@ def test_public_ipv4_freeipapi_com_net_error(): assert result["error"] == "No Internet" - - - def test_public_ipv4_ip_api_com_timeout_error(): result = get_public_ipv4(api=IPv4API.IP_API_COM, geo=True, timeout="5") assert not result["status"] @@ -240,9 +207,6 @@ def test_public_ipv4_ip_api_com_net_error(): assert result["error"] == "No Internet" - - - def test_public_ipv4_ipinfo_io_timeout_error(): result = get_public_ipv4(api=IPv4API.IPINFO_IO, geo=True, timeout="5") assert not result["status"] @@ -255,15 +219,11 @@ def test_public_ipv4_ipinfo_io_net_error(): assert result["error"] == "No Internet" - - - def test_public_ipv4_ip_sb_timeout_error(): result = get_public_ipv4(api=IPv4API.IP_SB, geo=True, timeout="5") assert not result["status"] - def test_public_ipv4_ip_sb_net_error(): with mock.patch.object(requests.Session, "get", side_effect=Exception("No Internet")): result = get_public_ipv4(api=IPv4API.IP_SB) @@ -271,9 +231,6 @@ def test_public_ipv4_ip_sb_net_error(): assert result["error"] == "No Internet" - - - def test_public_ipv4_ident_me_timeout_error(): result = get_public_ipv4(api=IPv4API.IDENT_ME, geo=True, timeout="5") assert not result["status"] @@ -286,9 +243,6 @@ def test_public_ipv4_ident_me_net_error(): assert result["error"] == "No Internet" - - - def test_public_ipv4_tnedi_me_timeout_error(): result = get_public_ipv4(api=IPv4API.TNEDI_ME, geo=True, timeout="5") assert not result["status"] @@ -307,9 +261,6 @@ def test_public_ipv4_api_error(): assert result["error"] == "Unsupported API: api1" - - - def test_public_ipv4_reallyfreegeoip_org_timeout_error(): result = get_public_ipv4(api=IPv4API.REALLYFREEGEOIP_ORG, geo=True, timeout="5") assert not result["status"] @@ -322,8 +273,6 @@ def test_public_ipv4_reallyfreegeoip_org_net_error(): assert result["error"] == "No Internet" - - def test_public_ipv4_wtfismyip_com_timeout_error(): result = get_public_ipv4(api=IPv4API.WTFISMYIP_COM, geo=True, timeout="5") assert not result["status"] @@ -334,3 +283,15 @@ def test_public_ipv4_wtfismyip_com_net_error(): result = get_public_ipv4(api=IPv4API.WTFISMYIP_COM) assert not result["status"] assert result["error"] == "No Internet" + + +def test_public_ipv4_db_ip_com_timeout_error(): + result = get_public_ipv4(api=IPv4API.DB_IP_COM, geo=True, timeout="5") + assert not result["status"] + + +def test_public_ipv4_db_ip_com_net_error(): + with mock.patch.object(requests.Session, "get", side_effect=Exception("No Internet")): + result = get_public_ipv4(api=IPv4API.DB_IP_COM) + assert not result["status"] + assert result["error"] == "No Internet" \ No newline at end of file