Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/pyapiary/api_connectors/ipqs.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@ def malicious_url(self, query: str, **kwargs) -> httpx.Response:
"""
return self.post("/url/", data={"url": query, "key": self.api_key, **kwargs})

@log_method_call
def phone_validation(self, query: str, **kwargs) -> httpx.Response:
"""The IPQS Phone Number Validation API offers rapid analysis to determine the risk core,
country of origin, carrier, validity, owner information, and connection status of phone numbers

Args:
query (str): The phone number to look up.

Returns:
httpx.Response: the httpx.Response object
"""
return self.post("/phone/", data={"phone": query, "key": self.api_key, **kwargs})


@bubble_broker_init_signature()
class AsyncIPQSConnector(AsyncBroker):
Expand Down Expand Up @@ -63,3 +76,16 @@ async def malicious_url(self, query: str, **kwargs) -> httpx.Response:
httpx.Response: the httpx.Response object
"""
return await self.post("/url/", data={"url": query, "key": self.api_key, **kwargs})

@log_method_call
async def phone_validation(self, query: str, **kwargs) -> httpx.Response:
"""The IPQS Phone Number Validation API offers rapid analysis to determine the risk core,
country of origin, carrier, validity, owner information, and connection status of phone numbers

Args:
query (str): The phone number to look up.

Returns:
httpx.Response: the httpx.Response object
"""
return await self.post("/phone/", data={"phone": query, "key": self.api_key, **kwargs})
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
interactions:
- request:
body: phone=12024567041&key=HOwg8USe8QTBYpdxDEnfAqWZeb0kpAuQ
headers:
accept:
- '*/*'
accept-encoding:
- gzip, deflate
connection:
- keep-alive
content-length:
- '54'
content-type:
- application/x-www-form-urlencoded
host:
- www.ipqualityscore.com
user-agent:
- REDACTED
method: POST
uri: https://www.ipqualityscore.com/api/json/phone/
response:
body:
string: !!binary |
H4sIAAAAAAAA/1VUXW/iOBT9K1aedjUM03a7s1KfcAE12aGBSaBVtbOKjGOIi2NnbYcqM+p/3+NA
aXmJcu+5Pvf7/opq4RzbiugmWlRGCyId2TMly2E0iFzLOeDoxttWDKKNsTXzXpQw/nR5dXF1/efX
vy6uL2GpDGeqOBgA/Q3g7wTw5yPeU554LGvLwnFj4fZiEFnBhfYFW7cOig1TDkYP82RxEhorGhYI
dKsUHki3604g417uxRs5Z9ZKYRHEg7Dyp9FkbOq61ZIzL412IVipReG7JiQ9Y7oMMtTctNpb8Ear
PIjSh/9HmsdJerecp9D9lE3BTRkeXl1c/PE1CsFvQQvFZAyplEhUb49Gl2/BFc4z36KQEe1lMoNL
8pnEclshPr2RpdA8BOFqV5SmZjJQpj++UOiYc4ZLhsIXAogqWFla9EWA8Ff0Tn0yI72ZIxtk1Dfy
IEc3/4QAjVDSi9E26Ibc1MAb1irwWOnZqHWleR5uzR76Z+NEU72gpEKfARZl69bCbv9rpfPnkFnr
TgE/84C2qBEv2V6WzmipXQsKLo7os3QVa6Q1o45Vxhy1O8meTaVhf+bAV6iP0w1TwtbmzEvJNFvb
1vHzNBSDtx1rENWZfiuM3QrHqxapnGfBKwyZ716k2IZUPkIbBN65GlPVjZhRpxR05yqpR5XZOG/Z
UJRt9O/rIMJQ26Ifg8NATbUXtgE7puD6E7ECJbSib1Ot+XvXa/5BUILtxGmBXMPqOsz4cQMCh3C+
CBuCLZXXfvfw8lThnWZ1GNa76Ty7m5LbVR4PyIKuZiRf0ixZ0gH5e55PFzF5pFk2TQcko+nkidxO
s7vvqyRfQjG/JenTDOoBGdNsRib0IZnk8zQ8TekypinJY7pIsnnQxCkg8i0B8zKe39Mc7mbT7B7g
hKaU3GarfAxwRlcZJd/oAswDcowvH8cruIbXcZzB+xN5TKZ3vetHfCnJ75MlMoC/x5Tcr7JF/ASn
SYggCfvpZS2w8iFligJh5398mQi9R60wHKbQxhc4VOpUOc8bVqwV4zuFdn84KRwT40VxvAnHfT6i
YUf7jXrvj25rjFCBS9bxcADCYr5f1rRHyQklG4GdtYKU0rG1QvPJAh3GRHCjPWaFuLZpjPXEG9KP
DmIZRm+XUnVHR+J0ERVD+8ML3IgSxm/6g+ogvb7+D6arKrXuBQAA
headers:
CF-RAY:
- 9e51976b889d98d5-ATL
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json; charset=UTF-8
Date:
- Tue, 31 Mar 2026 18:59:29 GMT
Nel:
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
Report-To:
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=MNIXz1eWc6WlUYSlvt%2FrHoZ5QQ62jQe%2BvrW%2B0dRfxNJU4OAh9YKgXohdqxSO3ED9eYP2ynVDPg4y7nwpTZdHvmVi8GcVjKsApuuMnMiWLjJXwliCb5Yq22rBsNTixyO5F%2BrJesPFCuc%3D"}]}'
Server:
- cloudflare
Transfer-Encoding:
- chunked
X-LB-ID:
- LB-3
X-Powered-By:
- IPQualityScore
X-Upstream-ID:
- N-152
cf-cache-status:
- DYNAMIC
status:
code: 200
message: OK
version: 1
13 changes: 12 additions & 1 deletion src/pyapiary/tests/test_ipqs/test_integration_ipqs.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,15 @@ def test_ipqs_malicious_url_vcr(vcr_cassette):

assert isinstance(result, httpx.Response)
assert "domain" in result.json()
assert result.json()["domain"] == "github.com"
assert result.json()["domain"] == "github.com"


@pytest.mark.integration
def test_ipqs_phone_validation_vcr(vcr_cassette):
with vcr_cassette.use_cassette("test_ipqs_phone_validation_vcr"):
connector = IPQSConnector(load_env_vars=True, enable_logging=True)
result = connector.phone_validation("+12024567041")

assert isinstance(result, httpx.Response)
assert "formatted" in result.json()
assert result.json()["formatted"] == "+12024567041"
27 changes: 27 additions & 0 deletions src/pyapiary/tests/test_ipqs/test_unit_async_ipqs.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,30 @@ async def test_async_malicious_url(mock_post):
"/url/",
data={"url": "example.com", "key": "test_key", "strictness": 1}
)


@patch("pyapiary.api_connectors.ipqs.AsyncIPQSConnector.post", new_callable=AsyncMock)
@pytest.mark.asyncio
async def test_async_phone_validation(mock_post):
import json

request = httpx.Request("POST", "https://www.ipqualityscore.com/api/json/url/")
payload = {"success": True, "phone": "8888888888"}
mock_response = httpx.Response(
200,
request=request,
content=json.dumps(payload).encode("utf-8"),
headers={"Content-Type": "application/json"},
)
mock_post.return_value = mock_response

connector = AsyncIPQSConnector(api_key="test_key")
response = await connector.phone_validation("8888888888", strictness=1)

assert isinstance(response, httpx.Response)
assert response.status_code == 200
assert response.json() == payload
mock_post.assert_awaited_once_with(
"/phone/",
data={"phone": "8888888888", "key": "test_key", "strictness": 1}
)
26 changes: 26 additions & 0 deletions src/pyapiary/tests/test_ipqs/test_unit_ipqs.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,29 @@ def test_malicious_url(mock_post):
)
assert isinstance(result, httpx.Response)
assert result.json() == payload


@patch("pyapiary.api_connectors.ipqs.IPQSConnector.post")
def test_phone_validation(mock_post):
# Build a real httpx.Response to match the new return type
import json

request = httpx.Request("POST", "https://www.ipqualityscore.com/api/json/phone/")
payload = {"success": True, "phone": "8888888888"}
mock_response = httpx.Response(
200,
request=request,
content=json.dumps(payload).encode("utf-8"),
headers={"Content-Type": "application/json"},
)
mock_post.return_value = mock_response

connector = IPQSConnector(api_key="test_key")
result = connector.phone_validation("8888888888")

mock_post.assert_called_once_with(
"/phone/",
data={"phone": "8888888888", "key": "test_key"},
)
assert isinstance(result, httpx.Response)
assert result.json() == payload
Loading