From 5b37bb14a8e6ed00ab39de608b24431b84b0d7cd Mon Sep 17 00:00:00 2001 From: sepandhaghighi Date: Tue, 20 May 2025 09:14:13 +0330 Subject: [PATCH 1/6] feat : _ipapi_co_ipv4 function added --- ipspot/ipv4.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/ipspot/ipv4.py b/ipspot/ipv4.py index a07c751..3a4b62d 100644 --- a/ipspot/ipv4.py +++ b/ipspot/ipv4.py @@ -112,6 +112,38 @@ def _ipsb_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]] return {"status": False, "error": str(e)} +def _ipapi_co_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]] + =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]: + """ + Get public IP and geolocation using ipapi.co. + + :param geo: geolocation flag + :param timeout: timeout value for API + """ + try: + with requests.Session() as session: + session.mount("http://", IPv4HTTPAdapter()) + session.mount("https://", IPv4HTTPAdapter()) + response = session.get("https://ipapi.co/json/", headers=REQUEST_HEADERS, timeout=timeout) + response.raise_for_status() + data = response.json() + result = {"status": True, "data": {"ip": data.get("ip"), "api": "ipapi.co"}} + if geo: + geo_data = { + "city": data.get("city"), + "region": data.get("region"), + "country": data.get("country_name"), + "country_code": data.get("country_code"), + "latitude": data.get("latitude"), + "longitude": data.get("longitude"), + "organization": data.get("org"), + "timezone": data.get("timezone") + } + result["data"].update(geo_data) + return result + except Exception as e: + return {"status": False, "error": str(e)} + def _ipapi_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]] =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]: """ From aacae1def0862235b839c88ccb5d35aff9850633 Mon Sep 17 00:00:00 2001 From: sepandhaghighi Date: Tue, 20 May 2025 09:16:25 +0330 Subject: [PATCH 2/6] fix : get_public_ipv4 function updated --- ipspot/ipv4.py | 1 + ipspot/params.py | 1 + 2 files changed, 2 insertions(+) diff --git a/ipspot/ipv4.py b/ipspot/ipv4.py index 3a4b62d..59857d0 100644 --- a/ipspot/ipv4.py +++ b/ipspot/ipv4.py @@ -290,6 +290,7 @@ def get_public_ipv4(api: IPv4API=IPv4API.AUTO, geo: bool=False, IPv4API.IPSB: _ipsb_ipv4, IPv4API.IPAPI: _ipapi_ipv4, IPv4API.IPINFO: _ipinfo_ipv4, + IPv4API.IPAPI_CO: _ipapi_co_ipv4 } if api == IPv4API.AUTO: diff --git a/ipspot/params.py b/ipspot/params.py index bd88cc6..5dfe0e3 100644 --- a/ipspot/params.py +++ b/ipspot/params.py @@ -23,6 +23,7 @@ class IPv4API(Enum): AUTO = "auto" IPAPI = "ipapi" + IPAPI_CO = "ipapi_co" IPINFO = "ipinfo" IPSB = "ipsb" IDENTME = "identme" From 155ca196e5f1f4502a711c88d3f8ff79bee7a640 Mon Sep 17 00:00:00 2001 From: sepandhaghighi Date: Tue, 20 May 2025 09:18:16 +0330 Subject: [PATCH 3/6] fix : tests updated --- tests/test_ipv4.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test_ipv4.py b/tests/test_ipv4.py index 4323830..6544f3f 100644 --- a/tests/test_ipv4.py +++ b/tests/test_ipv4.py @@ -93,6 +93,25 @@ def test_public_ipv4_auto_net_error(): assert not result["status"] assert result["error"] == "All attempts failed." +def test_public_ipv4_ipapi_co_success(): + result = get_public_ipv4(api=IPv4API.IPAPI_CO, geo=True) + assert result["status"] + assert is_ipv4(result["data"]["ip"]) + assert set(result["data"].keys()) == DATA_ITEMS + assert result["data"]["api"] == "ip-api.com" + + +def test_public_ipv4_ipapi_co_timeout_error(): + result = get_public_ipv4(api=IPv4API.IPAPI_CO, geo=True, timeout="5") + assert not result["status"] + + +def test_public_ipv4_ipapi_co_net_error(): + with mock.patch.object(requests.Session, "get", side_effect=Exception("No Internet")): + result = get_public_ipv4(api=IPv4API.IPAPI_CO) + assert not result["status"] + assert result["error"] == "No Internet" + def test_public_ipv4_ipapi_success(): result = get_public_ipv4(api=IPv4API.IPAPI, geo=True) From e1090945b8c1c3e9d8ac0a9290180d7de8bce240 Mon Sep 17 00:00:00 2001 From: sepandhaghighi Date: Tue, 20 May 2025 09:18:34 +0330 Subject: [PATCH 4/6] fix : autopep8 --- ipspot/ipv4.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ipspot/ipv4.py b/ipspot/ipv4.py index 59857d0..d235bef 100644 --- a/ipspot/ipv4.py +++ b/ipspot/ipv4.py @@ -113,7 +113,7 @@ def _ipsb_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]] def _ipapi_co_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]] - =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]: + =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]: """ Get public IP and geolocation using ipapi.co. @@ -144,6 +144,7 @@ def _ipapi_co_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]] except Exception as e: return {"status": False, "error": str(e)} + def _ipapi_ipv4(geo: bool=False, timeout: Union[float, Tuple[float, float]] =5) -> Dict[str, Union[bool, Dict[str, Union[str, float]], str]]: """ From 2f82221ca30cbfae12ad68561596db893c4f747c Mon Sep 17 00:00:00 2001 From: sepandhaghighi Date: Tue, 20 May 2025 09:20:25 +0330 Subject: [PATCH 5/6] doc : CHANGELOG.md updated --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index df37289..eabb6c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- Support [ipapi.co](https://ipapi.co/json/) ## [0.3] - 2025-05-19 ### Added - `is_ipv4` function From 6091c6b14d0b2e90d68a53274fe2ff2297d2e9d0 Mon Sep 17 00:00:00 2001 From: sepandhaghighi Date: Tue, 20 May 2025 09:25:05 +0330 Subject: [PATCH 6/6] fix : tests bug fixed --- README.md | 2 +- tests/test_ipv4.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 71ee288..51c895d 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,7 @@ Public IP and Location Info: #### IPv4 API -ℹ️ `ipv4-api` valid choices: [`auto`, `ipapi`, `ipinfo`, `ipsb`, `identme`, `tnedime`] +ℹ️ `ipv4-api` valid choices: [`auto`, `ipapi`, `ipinfo`, `ipsb`, `identme`, `tnedime`, `ipapi_co`] ℹ️ The default value: `auto` diff --git a/tests/test_ipv4.py b/tests/test_ipv4.py index 6544f3f..493c874 100644 --- a/tests/test_ipv4.py +++ b/tests/test_ipv4.py @@ -98,7 +98,7 @@ def test_public_ipv4_ipapi_co_success(): assert result["status"] assert is_ipv4(result["data"]["ip"]) assert set(result["data"].keys()) == DATA_ITEMS - assert result["data"]["api"] == "ip-api.com" + assert result["data"]["api"] == "ipapi.co" def test_public_ipv4_ipapi_co_timeout_error():