pyapiary 2.1.4__tar.gz → 2.2.0__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.
- {pyapiary-2.1.4 → pyapiary-2.2.0}/PKG-INFO +14 -1
- {pyapiary-2.1.4 → pyapiary-2.2.0}/README.md +10 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/pyproject.toml +4 -1
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/api_connectors/ipqs.py +26 -0
- pyapiary-2.2.0/src/pyapiary/dbms_connectors/postgres.py +104 -0
- pyapiary-2.2.0/src/pyapiary/tests/test_ipqs/__init__.py +0 -0
- pyapiary-2.2.0/src/pyapiary/tests/test_ipqs/cassettes/test_ipqs_phone_validation_vcr.yaml +70 -0
- pyapiary-2.2.0/src/pyapiary/tests/test_ipqs/test_integration_ipqs.py +24 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_ipqs/test_unit_async_ipqs.py +27 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_ipqs/test_unit_ipqs.py +26 -0
- pyapiary-2.2.0/src/pyapiary/tests/test_postgres/test_unit_postgres.py +259 -0
- pyapiary-2.1.4/src/pyapiary/tests/test_ipqs/test_integration_ipqs.py +0 -13
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/__init__.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/api_connectors/__init__.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/api_connectors/broker.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/api_connectors/domaintools.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/api_connectors/flashpoint.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/api_connectors/generic.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/api_connectors/spycloud.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/api_connectors/twilio.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/api_connectors/urlscan.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/dbms_connectors/__init__.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/dbms_connectors/elasticsearch.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/dbms_connectors/mongo.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/dbms_connectors/mongo_async.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/dbms_connectors/odbc.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/dbms_connectors/splunk.py +0 -0
- /pyapiary-2.1.4/src/pyapiary/tests/__init__.py → /pyapiary-2.2.0/src/pyapiary/dbms_connectors/trino.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/helpers.py +0 -0
- {pyapiary-2.1.4/src/pyapiary/tests/test_ipqs → pyapiary-2.2.0/src/pyapiary/tests}/__init__.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/conftest.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_broker/test_integration_broker.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_broker/test_unit_asyncbroker.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_broker/test_unit_broker.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_domaintools/cassettes/.gitkeep +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_domaintools/cassettes/test_domaintools_iris_investigate_vcr.yaml +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_domaintools/cassettes/test_domaintools_parsed_whois_vcr.yaml +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_domaintools/test_integration_domaintools.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_domaintools/test_unit_async_domaintools.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_domaintools/test_unit_domaintools.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_elasticsearch/test_unit_elasticsearch.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_flashpoint/cassettes/test_flashpoint_search_fraud_vcr.yaml +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_flashpoint/test_integration_flashpoint.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_flashpoint/test_unit_async_flashpoint.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_flashpoint/test_unit_flashpoint.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_generic/cassettes/test_generic_get_github_api.yaml +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_generic/test_integration_generic_connector.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_generic/test_unit_async_generic_connector.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_generic/test_unit_generic_connector.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_ipqs/cassettes/test_ipqs_malicious_url_vcr.yaml +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_mongodb/test_unit_async_mongo.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_mongodb/test_unit_mongo.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_odbc/test_unit_odbc.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_splunk/test_unit_splunk.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_spycloud/cassettes/test_spycloud_ato_search_vcr.yaml +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_spycloud/test_integration_spycloud.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_spycloud/test_unit_async_spycloud.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_spycloud/test_unit_spycloud.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_twilio/cassettes/test_lookup_phone_vcr.yaml +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_twilio/test_integration_twilio.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_twilio/test_unit_async_twilio.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_twilio/test_unit_twilio.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_urlscan/cassettes/test_urlscan_results_vcr.yaml +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_urlscan/test_integration_urlscan.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_urlscan/test_unit_async_urlscan.py +0 -0
- {pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_urlscan/test_unit_urlscan.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyapiary
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: A simple, lightweight set of connectors and functions to various APIs and DBMSs, controlled by a central broker.
|
|
5
5
|
Author: Rob D'Aveta
|
|
6
6
|
Author-email: rob.daveta@gmail.com
|
|
@@ -14,6 +14,9 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
14
14
|
Provides-Extra: odbc
|
|
15
15
|
Requires-Dist: elasticsearch (>=9.1.1,<10.0.0)
|
|
16
16
|
Requires-Dist: httpx (>=0.28.1,<0.29.0)
|
|
17
|
+
Requires-Dist: psycopg-binary (>=3.3.4,<4.0.0)
|
|
18
|
+
Requires-Dist: psycopg-pool[binary,pool] (>=3.3.1,<4.0.0)
|
|
19
|
+
Requires-Dist: psycopg[pool] (>=3.3.4,<4.0.0)
|
|
17
20
|
Requires-Dist: pymongo (>=4.15.0,<5.0.0)
|
|
18
21
|
Requires-Dist: pyodbc (>=5.3.0,<6.0.0) ; extra == "odbc"
|
|
19
22
|
Requires-Dist: python-dotenv (>=1.1.1,<2.0.0)
|
|
@@ -355,6 +358,16 @@ results = conn.query("search index=_internal | head 5")
|
|
|
355
358
|
pytest -m integration
|
|
356
359
|
```
|
|
357
360
|
|
|
361
|
+
|
|
362
|
+
### Manual Testing
|
|
363
|
+
- Located in dev_env
|
|
364
|
+
- Do not use pytest or mocking
|
|
365
|
+
- run the docker-compose.yaml to stand up services, change directories into the folder in dev_env you wish to test
|
|
366
|
+
- execute the following to test your module
|
|
367
|
+
```
|
|
368
|
+
poetry run python <your test file here>.py
|
|
369
|
+
```
|
|
370
|
+
|
|
358
371
|
### 🧼 Suppress warnings
|
|
359
372
|
|
|
360
373
|
Add this to `pytest.ini`:
|
|
@@ -330,6 +330,16 @@ results = conn.query("search index=_internal | head 5")
|
|
|
330
330
|
pytest -m integration
|
|
331
331
|
```
|
|
332
332
|
|
|
333
|
+
|
|
334
|
+
### Manual Testing
|
|
335
|
+
- Located in dev_env
|
|
336
|
+
- Do not use pytest or mocking
|
|
337
|
+
- run the docker-compose.yaml to stand up services, change directories into the folder in dev_env you wish to test
|
|
338
|
+
- execute the following to test your module
|
|
339
|
+
```
|
|
340
|
+
poetry run python <your test file here>.py
|
|
341
|
+
```
|
|
342
|
+
|
|
333
343
|
### 🧼 Suppress warnings
|
|
334
344
|
|
|
335
345
|
Add this to `pytest.ini`:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "pyapiary"
|
|
3
3
|
packages = [{ include = "pyapiary", from = "src" }]
|
|
4
|
-
version = "2.
|
|
4
|
+
version = "2.2.0"
|
|
5
5
|
description = "A simple, lightweight set of connectors and functions to various APIs and DBMSs, controlled by a central broker."
|
|
6
6
|
authors = ["Rob D'Aveta <rob.daveta@gmail.com>"]
|
|
7
7
|
readme = "README.md"
|
|
@@ -17,6 +17,9 @@ splunk-sdk = "^2.1.1"
|
|
|
17
17
|
httpx = "^0.28.1"
|
|
18
18
|
tenacity = "^9.1.2"
|
|
19
19
|
pyodbc = "^5.3.0"
|
|
20
|
+
psycopg-pool = {extras = ["binary", "pool"], version = "^3.3.1"}
|
|
21
|
+
psycopg = {extras = ["pool"], version = "^3.3.4"}
|
|
22
|
+
psycopg-binary = "^3.3.4"
|
|
20
23
|
|
|
21
24
|
[tool.poetry.extras]
|
|
22
25
|
odbc = ["pyodbc"]
|
|
@@ -36,6 +36,19 @@ class IPQSConnector(Broker):
|
|
|
36
36
|
"""
|
|
37
37
|
return self.post("/url/", data={"url": query, "key": self.api_key, **kwargs})
|
|
38
38
|
|
|
39
|
+
@log_method_call
|
|
40
|
+
def phone_validation(self, query: str, **kwargs) -> httpx.Response:
|
|
41
|
+
"""The IPQS Phone Number Validation API offers rapid analysis to determine the risk core,
|
|
42
|
+
country of origin, carrier, validity, owner information, and connection status of phone numbers
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
query (str): The phone number to look up.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
httpx.Response: the httpx.Response object
|
|
49
|
+
"""
|
|
50
|
+
return self.post("/phone/", data={"phone": query, "key": self.api_key, **kwargs})
|
|
51
|
+
|
|
39
52
|
|
|
40
53
|
@bubble_broker_init_signature()
|
|
41
54
|
class AsyncIPQSConnector(AsyncBroker):
|
|
@@ -63,3 +76,16 @@ class AsyncIPQSConnector(AsyncBroker):
|
|
|
63
76
|
httpx.Response: the httpx.Response object
|
|
64
77
|
"""
|
|
65
78
|
return await self.post("/url/", data={"url": query, "key": self.api_key, **kwargs})
|
|
79
|
+
|
|
80
|
+
@log_method_call
|
|
81
|
+
async def phone_validation(self, query: str, **kwargs) -> httpx.Response:
|
|
82
|
+
"""The IPQS Phone Number Validation API offers rapid analysis to determine the risk core,
|
|
83
|
+
country of origin, carrier, validity, owner information, and connection status of phone numbers
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
query (str): The phone number to look up.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
httpx.Response: the httpx.Response object
|
|
90
|
+
"""
|
|
91
|
+
return await self.post("/phone/", data={"phone": query, "key": self.api_key, **kwargs})
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from typing import List, Dict, Any, Optional, Generator, Type, Union
|
|
2
|
+
from types import TracebackType
|
|
3
|
+
from pyapiary.helpers import setup_logger
|
|
4
|
+
from psycopg_pool import AsyncConnectionPool, ConnectionPool
|
|
5
|
+
|
|
6
|
+
class PostgresConnector:
|
|
7
|
+
def __init__(self,conn_str, logger=None, min_size=5, max_size=30):
|
|
8
|
+
self.dsn = conn_str
|
|
9
|
+
self.min_size = min_size
|
|
10
|
+
self.max_size = max_size
|
|
11
|
+
self.connection_pool = ConnectionPool(self.dsn, kwargs={"autocommit":True}, min_size=self.min_size, max_size=self.max_size)
|
|
12
|
+
self.logger = logger if logger else setup_logger(__name__)
|
|
13
|
+
|
|
14
|
+
def __enter__(self):
|
|
15
|
+
return self
|
|
16
|
+
|
|
17
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
18
|
+
self.close()
|
|
19
|
+
|
|
20
|
+
def close(self):
|
|
21
|
+
"""Close the PG connection."""
|
|
22
|
+
if self.connection_pool:
|
|
23
|
+
self.connection_pool.close()
|
|
24
|
+
self._log("PG connection closed")
|
|
25
|
+
|
|
26
|
+
def _log(self, msg: str, level: str = "info"):
|
|
27
|
+
if self.logger:
|
|
28
|
+
log_method = getattr(self.logger, level, self.logger.info)
|
|
29
|
+
log_method(msg)
|
|
30
|
+
|
|
31
|
+
def query(self, query: str, params=None):
|
|
32
|
+
"""
|
|
33
|
+
query - string query representing the work the user wants done
|
|
34
|
+
params - must be legal for psycopog_pool AsyncConnectionPool object
|
|
35
|
+
https://www.psycopg.org/psycopg3/docs/api/pool.html#module-psycopg_pool
|
|
36
|
+
"""
|
|
37
|
+
with self.connection_pool.connection() as conn:
|
|
38
|
+
with conn.transaction():
|
|
39
|
+
# claude recommended a transaction wrapper here
|
|
40
|
+
return conn.execute(query, params).fetchall()
|
|
41
|
+
|
|
42
|
+
def bulk_insert(self, table: str, data: List[Dict[str, Any]]):
|
|
43
|
+
if not data:
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
self._log(f"Inserting {len(data)} rows into table {table}")
|
|
47
|
+
|
|
48
|
+
columns = list(data[0].keys())
|
|
49
|
+
copy_query = f"COPY {table} ({', '.join(columns)}) FROM STDIN"
|
|
50
|
+
|
|
51
|
+
with self.connection_pool.connection() as conn:
|
|
52
|
+
with conn.cursor() as cur:
|
|
53
|
+
with cur.copy(copy_query) as copy:
|
|
54
|
+
for row in data:
|
|
55
|
+
copy.write_row(tuple(row[col] for col in columns))
|
|
56
|
+
# Note: pool is configured for autocommit, commit will happen before the with block ends
|
|
57
|
+
|
|
58
|
+
# Async Version
|
|
59
|
+
## Need to write an async_bulk_insert
|
|
60
|
+
class AsyncPostgresConnector:
|
|
61
|
+
def __init__(self,conn_str, min_size=5, max_size=30, logger=None):
|
|
62
|
+
self.dsn = conn_str
|
|
63
|
+
self.min_size = min_size
|
|
64
|
+
self.max_size = max_size
|
|
65
|
+
self.connection_pool = AsyncConnectionPool(self.dsn, kwargs={"autocommit":True}, min_size=self.min_size, max_size=self.max_size, open=False)
|
|
66
|
+
self.logger = logger if logger else setup_logger(__name__)
|
|
67
|
+
|
|
68
|
+
async def __aenter__(self):
|
|
69
|
+
# for async with calls
|
|
70
|
+
await self.connection_pool.open()
|
|
71
|
+
return self
|
|
72
|
+
|
|
73
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
74
|
+
# for async with calls
|
|
75
|
+
await self.connection_pool.close()
|
|
76
|
+
|
|
77
|
+
async def async_query(self, query: str, params=None):
|
|
78
|
+
"""
|
|
79
|
+
query - string query representing the work the user wants done
|
|
80
|
+
params - must be legal for psycopog_pool AsyncConnectionPool object
|
|
81
|
+
https://www.psycopg.org/psycopg3/docs/api/pool.html#module-psycopg_pool
|
|
82
|
+
"""
|
|
83
|
+
async with self.connection_pool.connection() as conn:
|
|
84
|
+
cur = await conn.execute(query, params)
|
|
85
|
+
return await cur.fetchall()
|
|
86
|
+
|
|
87
|
+
async def async_bulk_insert(self, table_name: str, data: List[Dict[str, Any]]):
|
|
88
|
+
if not data:
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
columns = list(data[0].keys())
|
|
92
|
+
|
|
93
|
+
# Google recommended using an cursor.copy command here to process the dict, better performance over a high volume of rows, more efficient
|
|
94
|
+
# than the odbc write/execute_many paradigm.
|
|
95
|
+
async with self.connection_pool.connection() as aconn:
|
|
96
|
+
async with aconn.cursor() as acur:
|
|
97
|
+
# using COPY is the most performative for millions of rows
|
|
98
|
+
copy_query = f"COPY {table_name} ({', '.join(columns)}) FROM STDIN"
|
|
99
|
+
|
|
100
|
+
async with acur.copy(copy_query) as copy:
|
|
101
|
+
for record in data:
|
|
102
|
+
row = tuple(record[col] for col in columns)
|
|
103
|
+
await copy.write_row(row)
|
|
104
|
+
# Note: since asyncpool passes autocommit kwarg, commits will happen before the with block ends
|
|
File without changes
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
interactions:
|
|
2
|
+
- request:
|
|
3
|
+
body: phone=12024567041&key=HOwg8USe8QTBYpdxDEnfAqWZeb0kpAuQ
|
|
4
|
+
headers:
|
|
5
|
+
accept:
|
|
6
|
+
- '*/*'
|
|
7
|
+
accept-encoding:
|
|
8
|
+
- gzip, deflate
|
|
9
|
+
connection:
|
|
10
|
+
- keep-alive
|
|
11
|
+
content-length:
|
|
12
|
+
- '54'
|
|
13
|
+
content-type:
|
|
14
|
+
- application/x-www-form-urlencoded
|
|
15
|
+
host:
|
|
16
|
+
- www.ipqualityscore.com
|
|
17
|
+
user-agent:
|
|
18
|
+
- REDACTED
|
|
19
|
+
method: POST
|
|
20
|
+
uri: https://www.ipqualityscore.com/api/json/phone/
|
|
21
|
+
response:
|
|
22
|
+
body:
|
|
23
|
+
string: !!binary |
|
|
24
|
+
H4sIAAAAAAAA/1VUXW/iOBT9K1aedjUM03a7s1KfcAE12aGBSaBVtbOKjGOIi2NnbYcqM+p/3+NA
|
|
25
|
+
aXmJcu+5Pvf7/opq4RzbiugmWlRGCyId2TMly2E0iFzLOeDoxttWDKKNsTXzXpQw/nR5dXF1/efX
|
|
26
|
+
vy6uL2GpDGeqOBgA/Q3g7wTw5yPeU554LGvLwnFj4fZiEFnBhfYFW7cOig1TDkYP82RxEhorGhYI
|
|
27
|
+
dKsUHki3604g417uxRs5Z9ZKYRHEg7Dyp9FkbOq61ZIzL412IVipReG7JiQ9Y7oMMtTctNpb8Ear
|
|
28
|
+
PIjSh/9HmsdJerecp9D9lE3BTRkeXl1c/PE1CsFvQQvFZAyplEhUb49Gl2/BFc4z36KQEe1lMoNL
|
|
29
|
+
8pnEclshPr2RpdA8BOFqV5SmZjJQpj++UOiYc4ZLhsIXAogqWFla9EWA8Ff0Tn0yI72ZIxtk1Dfy
|
|
30
|
+
IEc3/4QAjVDSi9E26Ibc1MAb1irwWOnZqHWleR5uzR76Z+NEU72gpEKfARZl69bCbv9rpfPnkFnr
|
|
31
|
+
TgE/84C2qBEv2V6WzmipXQsKLo7os3QVa6Q1o45Vxhy1O8meTaVhf+bAV6iP0w1TwtbmzEvJNFvb
|
|
32
|
+
1vHzNBSDtx1rENWZfiuM3QrHqxapnGfBKwyZ716k2IZUPkIbBN65GlPVjZhRpxR05yqpR5XZOG/Z
|
|
33
|
+
UJRt9O/rIMJQ26Ifg8NATbUXtgE7puD6E7ECJbSib1Ot+XvXa/5BUILtxGmBXMPqOsz4cQMCh3C+
|
|
34
|
+
CBuCLZXXfvfw8lThnWZ1GNa76Ty7m5LbVR4PyIKuZiRf0ixZ0gH5e55PFzF5pFk2TQcko+nkidxO
|
|
35
|
+
s7vvqyRfQjG/JenTDOoBGdNsRib0IZnk8zQ8TekypinJY7pIsnnQxCkg8i0B8zKe39Mc7mbT7B7g
|
|
36
|
+
hKaU3GarfAxwRlcZJd/oAswDcowvH8cruIbXcZzB+xN5TKZ3vetHfCnJ75MlMoC/x5Tcr7JF/ASn
|
|
37
|
+
SYggCfvpZS2w8iFligJh5398mQi9R60wHKbQxhc4VOpUOc8bVqwV4zuFdn84KRwT40VxvAnHfT6i
|
|
38
|
+
YUf7jXrvj25rjFCBS9bxcADCYr5f1rRHyQklG4GdtYKU0rG1QvPJAh3GRHCjPWaFuLZpjPXEG9KP
|
|
39
|
+
DmIZRm+XUnVHR+J0ERVD+8ML3IgSxm/6g+ogvb7+D6arKrXuBQAA
|
|
40
|
+
headers:
|
|
41
|
+
CF-RAY:
|
|
42
|
+
- 9e51976b889d98d5-ATL
|
|
43
|
+
Connection:
|
|
44
|
+
- keep-alive
|
|
45
|
+
Content-Encoding:
|
|
46
|
+
- gzip
|
|
47
|
+
Content-Type:
|
|
48
|
+
- application/json; charset=UTF-8
|
|
49
|
+
Date:
|
|
50
|
+
- Tue, 31 Mar 2026 18:59:29 GMT
|
|
51
|
+
Nel:
|
|
52
|
+
- '{"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}'
|
|
53
|
+
Report-To:
|
|
54
|
+
- '{"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=MNIXz1eWc6WlUYSlvt%2FrHoZ5QQ62jQe%2BvrW%2B0dRfxNJU4OAh9YKgXohdqxSO3ED9eYP2ynVDPg4y7nwpTZdHvmVi8GcVjKsApuuMnMiWLjJXwliCb5Yq22rBsNTixyO5F%2BrJesPFCuc%3D"}]}'
|
|
55
|
+
Server:
|
|
56
|
+
- cloudflare
|
|
57
|
+
Transfer-Encoding:
|
|
58
|
+
- chunked
|
|
59
|
+
X-LB-ID:
|
|
60
|
+
- LB-3
|
|
61
|
+
X-Powered-By:
|
|
62
|
+
- IPQualityScore
|
|
63
|
+
X-Upstream-ID:
|
|
64
|
+
- N-152
|
|
65
|
+
cf-cache-status:
|
|
66
|
+
- DYNAMIC
|
|
67
|
+
status:
|
|
68
|
+
code: 200
|
|
69
|
+
message: OK
|
|
70
|
+
version: 1
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
import pytest
|
|
3
|
+
from pyapiary.api_connectors.ipqs import IPQSConnector
|
|
4
|
+
|
|
5
|
+
@pytest.mark.integration
|
|
6
|
+
def test_ipqs_malicious_url_vcr(vcr_cassette):
|
|
7
|
+
with vcr_cassette.use_cassette("test_ipqs_malicious_url_vcr"):
|
|
8
|
+
connector = IPQSConnector(load_env_vars=True, enable_logging=True)
|
|
9
|
+
result = connector.malicious_url("github.com")
|
|
10
|
+
|
|
11
|
+
assert isinstance(result, httpx.Response)
|
|
12
|
+
assert "domain" in result.json()
|
|
13
|
+
assert result.json()["domain"] == "github.com"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.mark.integration
|
|
17
|
+
def test_ipqs_phone_validation_vcr(vcr_cassette):
|
|
18
|
+
with vcr_cassette.use_cassette("test_ipqs_phone_validation_vcr"):
|
|
19
|
+
connector = IPQSConnector(load_env_vars=True, enable_logging=True)
|
|
20
|
+
result = connector.phone_validation("+12024567041")
|
|
21
|
+
|
|
22
|
+
assert isinstance(result, httpx.Response)
|
|
23
|
+
assert "formatted" in result.json()
|
|
24
|
+
assert result.json()["formatted"] == "+12024567041"
|
|
@@ -51,3 +51,30 @@ async def test_async_malicious_url(mock_post):
|
|
|
51
51
|
"/url/",
|
|
52
52
|
data={"url": "example.com", "key": "test_key", "strictness": 1}
|
|
53
53
|
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@patch("pyapiary.api_connectors.ipqs.AsyncIPQSConnector.post", new_callable=AsyncMock)
|
|
57
|
+
@pytest.mark.asyncio
|
|
58
|
+
async def test_async_phone_validation(mock_post):
|
|
59
|
+
import json
|
|
60
|
+
|
|
61
|
+
request = httpx.Request("POST", "https://www.ipqualityscore.com/api/json/url/")
|
|
62
|
+
payload = {"success": True, "phone": "8888888888"}
|
|
63
|
+
mock_response = httpx.Response(
|
|
64
|
+
200,
|
|
65
|
+
request=request,
|
|
66
|
+
content=json.dumps(payload).encode("utf-8"),
|
|
67
|
+
headers={"Content-Type": "application/json"},
|
|
68
|
+
)
|
|
69
|
+
mock_post.return_value = mock_response
|
|
70
|
+
|
|
71
|
+
connector = AsyncIPQSConnector(api_key="test_key")
|
|
72
|
+
response = await connector.phone_validation("8888888888", strictness=1)
|
|
73
|
+
|
|
74
|
+
assert isinstance(response, httpx.Response)
|
|
75
|
+
assert response.status_code == 200
|
|
76
|
+
assert response.json() == payload
|
|
77
|
+
mock_post.assert_awaited_once_with(
|
|
78
|
+
"/phone/",
|
|
79
|
+
data={"phone": "8888888888", "key": "test_key", "strictness": 1}
|
|
80
|
+
)
|
|
@@ -46,3 +46,29 @@ def test_malicious_url(mock_post):
|
|
|
46
46
|
)
|
|
47
47
|
assert isinstance(result, httpx.Response)
|
|
48
48
|
assert result.json() == payload
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@patch("pyapiary.api_connectors.ipqs.IPQSConnector.post")
|
|
52
|
+
def test_phone_validation(mock_post):
|
|
53
|
+
# Build a real httpx.Response to match the new return type
|
|
54
|
+
import json
|
|
55
|
+
|
|
56
|
+
request = httpx.Request("POST", "https://www.ipqualityscore.com/api/json/phone/")
|
|
57
|
+
payload = {"success": True, "phone": "8888888888"}
|
|
58
|
+
mock_response = httpx.Response(
|
|
59
|
+
200,
|
|
60
|
+
request=request,
|
|
61
|
+
content=json.dumps(payload).encode("utf-8"),
|
|
62
|
+
headers={"Content-Type": "application/json"},
|
|
63
|
+
)
|
|
64
|
+
mock_post.return_value = mock_response
|
|
65
|
+
|
|
66
|
+
connector = IPQSConnector(api_key="test_key")
|
|
67
|
+
result = connector.phone_validation("8888888888")
|
|
68
|
+
|
|
69
|
+
mock_post.assert_called_once_with(
|
|
70
|
+
"/phone/",
|
|
71
|
+
data={"phone": "8888888888", "key": "test_key"},
|
|
72
|
+
)
|
|
73
|
+
assert isinstance(result, httpx.Response)
|
|
74
|
+
assert result.json() == payload
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from unittest.mock import MagicMock, AsyncMock, patch
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
# Mock psycopg_pool before importing the module so tests run even when the
|
|
7
|
+
# driver is not installed in the environment.
|
|
8
|
+
sys.modules.setdefault("psycopg_pool", MagicMock())
|
|
9
|
+
|
|
10
|
+
from pyapiary.dbms_connectors.postgres import PostgresConnector, AsyncPostgresConnector
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# ──────────────────────────────────────────────
|
|
14
|
+
# Sync PostgresConnector
|
|
15
|
+
# ──────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
@pytest.fixture
|
|
18
|
+
def pg():
|
|
19
|
+
with patch("pyapiary.dbms_connectors.postgres.ConnectionPool"):
|
|
20
|
+
yield PostgresConnector("postgresql://user:pass@localhost/testdb")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TestPostgresConnectorInit:
|
|
24
|
+
@patch("pyapiary.dbms_connectors.postgres.ConnectionPool")
|
|
25
|
+
def test_creates_pool_with_defaults(self, mock_pool):
|
|
26
|
+
conn = PostgresConnector("postgresql://localhost/db")
|
|
27
|
+
mock_pool.assert_called_once_with(
|
|
28
|
+
"postgresql://localhost/db",
|
|
29
|
+
kwargs={"autocommit": True},
|
|
30
|
+
min_size=5,
|
|
31
|
+
max_size=30,
|
|
32
|
+
)
|
|
33
|
+
assert conn.dsn == "postgresql://localhost/db"
|
|
34
|
+
assert conn.min_size == 5
|
|
35
|
+
assert conn.max_size == 30
|
|
36
|
+
|
|
37
|
+
@patch("pyapiary.dbms_connectors.postgres.ConnectionPool")
|
|
38
|
+
def test_creates_pool_with_custom_sizes(self, mock_pool):
|
|
39
|
+
conn = PostgresConnector("dsn", min_size=1, max_size=10)
|
|
40
|
+
mock_pool.assert_called_once_with(
|
|
41
|
+
"dsn",
|
|
42
|
+
kwargs={"autocommit": True},
|
|
43
|
+
min_size=1,
|
|
44
|
+
max_size=10,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
@patch("pyapiary.dbms_connectors.postgres.ConnectionPool")
|
|
48
|
+
def test_accepts_custom_logger(self, mock_pool):
|
|
49
|
+
logger = MagicMock()
|
|
50
|
+
conn = PostgresConnector("dsn", logger=logger)
|
|
51
|
+
assert conn.logger is logger
|
|
52
|
+
|
|
53
|
+
@patch("pyapiary.dbms_connectors.postgres.ConnectionPool")
|
|
54
|
+
def test_uses_default_logger_when_none(self, mock_pool):
|
|
55
|
+
conn = PostgresConnector("dsn")
|
|
56
|
+
assert conn.logger is not None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class TestPostgresConnectorContextManager:
|
|
60
|
+
def test_enter_returns_self(self, pg):
|
|
61
|
+
assert pg.__enter__() is pg
|
|
62
|
+
|
|
63
|
+
def test_exit_closes_pool(self, pg):
|
|
64
|
+
pg.__exit__(None, None, None)
|
|
65
|
+
pg.connection_pool.close.assert_called_once()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class TestPostgresConnectorClose:
|
|
69
|
+
def test_close_closes_pool(self, pg):
|
|
70
|
+
pg.close()
|
|
71
|
+
pg.connection_pool.close.assert_called_once()
|
|
72
|
+
|
|
73
|
+
def test_close_when_pool_is_none(self, pg):
|
|
74
|
+
pg.connection_pool = None
|
|
75
|
+
pg.close() # should not raise
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class TestPostgresConnectorQuery:
|
|
79
|
+
def test_query_returns_results(self, pg):
|
|
80
|
+
mock_conn = MagicMock()
|
|
81
|
+
mock_conn.execute.return_value.fetchall.return_value = [("row1",), ("row2",)]
|
|
82
|
+
pg.connection_pool.connection.return_value.__enter__ = MagicMock(return_value=mock_conn)
|
|
83
|
+
pg.connection_pool.connection.return_value.__exit__ = MagicMock(return_value=False)
|
|
84
|
+
mock_conn.transaction.return_value.__enter__ = MagicMock()
|
|
85
|
+
mock_conn.transaction.return_value.__exit__ = MagicMock(return_value=False)
|
|
86
|
+
|
|
87
|
+
result = pg.query("SELECT 1")
|
|
88
|
+
assert result == [("row1",), ("row2",)]
|
|
89
|
+
mock_conn.execute.assert_called_once_with("SELECT 1", None)
|
|
90
|
+
|
|
91
|
+
def test_query_passes_params(self, pg):
|
|
92
|
+
mock_conn = MagicMock()
|
|
93
|
+
mock_conn.execute.return_value.fetchall.return_value = []
|
|
94
|
+
pg.connection_pool.connection.return_value.__enter__ = MagicMock(return_value=mock_conn)
|
|
95
|
+
pg.connection_pool.connection.return_value.__exit__ = MagicMock(return_value=False)
|
|
96
|
+
mock_conn.transaction.return_value.__enter__ = MagicMock()
|
|
97
|
+
mock_conn.transaction.return_value.__exit__ = MagicMock(return_value=False)
|
|
98
|
+
|
|
99
|
+
pg.query("SELECT * FROM t WHERE id = %s", (42,))
|
|
100
|
+
mock_conn.execute.assert_called_once_with("SELECT * FROM t WHERE id = %s", (42,))
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class TestPostgresConnectorBulkInsert:
|
|
104
|
+
def test_empty_data_returns_immediately(self, pg):
|
|
105
|
+
pg.bulk_insert("my_table", [])
|
|
106
|
+
pg.connection_pool.connection.assert_not_called()
|
|
107
|
+
|
|
108
|
+
def test_bulk_insert_calls_copy(self, pg):
|
|
109
|
+
mock_copy = MagicMock()
|
|
110
|
+
mock_cursor = MagicMock()
|
|
111
|
+
mock_cursor.copy.return_value.__enter__ = MagicMock(return_value=mock_copy)
|
|
112
|
+
mock_cursor.copy.return_value.__exit__ = MagicMock(return_value=False)
|
|
113
|
+
|
|
114
|
+
mock_conn = MagicMock()
|
|
115
|
+
mock_conn.cursor.return_value.__enter__ = MagicMock(return_value=mock_cursor)
|
|
116
|
+
mock_conn.cursor.return_value.__exit__ = MagicMock(return_value=False)
|
|
117
|
+
|
|
118
|
+
pg.connection_pool.connection.return_value.__enter__ = MagicMock(return_value=mock_conn)
|
|
119
|
+
pg.connection_pool.connection.return_value.__exit__ = MagicMock(return_value=False)
|
|
120
|
+
|
|
121
|
+
data = [{"name": "alice", "age": 30}, {"name": "bob", "age": 25}]
|
|
122
|
+
pg.bulk_insert("users", data)
|
|
123
|
+
|
|
124
|
+
mock_cursor.copy.assert_called_once_with("COPY users (name, age) FROM STDIN")
|
|
125
|
+
assert mock_copy.write_row.call_count == 2
|
|
126
|
+
mock_copy.write_row.assert_any_call(("alice", 30))
|
|
127
|
+
mock_copy.write_row.assert_any_call(("bob", 25))
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class TestPostgresConnectorLog:
|
|
131
|
+
def test_log_default_level(self, pg):
|
|
132
|
+
pg.logger = MagicMock()
|
|
133
|
+
pg._log("test message")
|
|
134
|
+
pg.logger.info.assert_called_once_with("test message")
|
|
135
|
+
|
|
136
|
+
def test_log_custom_level(self, pg):
|
|
137
|
+
pg.logger = MagicMock()
|
|
138
|
+
pg._log("warning msg", level="warning")
|
|
139
|
+
pg.logger.warning.assert_called_once_with("warning msg")
|
|
140
|
+
|
|
141
|
+
def test_log_falls_back_to_info(self, pg):
|
|
142
|
+
pg.logger = MagicMock(spec=["info"])
|
|
143
|
+
pg.logger.info = MagicMock()
|
|
144
|
+
pg._log("msg", level="nonexistent")
|
|
145
|
+
pg.logger.info.assert_called_once_with("msg")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# ──────────────────────────────────────────────
|
|
149
|
+
# Async AsyncPostgresConnector
|
|
150
|
+
# ──────────────────────────────────────────────
|
|
151
|
+
|
|
152
|
+
@pytest.fixture
|
|
153
|
+
def async_pg():
|
|
154
|
+
with patch("pyapiary.dbms_connectors.postgres.AsyncConnectionPool"):
|
|
155
|
+
yield AsyncPostgresConnector("postgresql://user:pass@localhost/testdb")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class TestAsyncPostgresConnectorInit:
|
|
159
|
+
@patch("pyapiary.dbms_connectors.postgres.AsyncConnectionPool")
|
|
160
|
+
def test_creates_pool_with_defaults(self, mock_pool):
|
|
161
|
+
conn = AsyncPostgresConnector("postgresql://localhost/db")
|
|
162
|
+
mock_pool.assert_called_once_with(
|
|
163
|
+
"postgresql://localhost/db",
|
|
164
|
+
kwargs={"autocommit": True},
|
|
165
|
+
min_size=5,
|
|
166
|
+
max_size=30,
|
|
167
|
+
open=False,
|
|
168
|
+
)
|
|
169
|
+
assert conn.dsn == "postgresql://localhost/db"
|
|
170
|
+
|
|
171
|
+
@patch("pyapiary.dbms_connectors.postgres.AsyncConnectionPool")
|
|
172
|
+
def test_creates_pool_with_custom_sizes(self, mock_pool):
|
|
173
|
+
conn = AsyncPostgresConnector("dsn", min_size=2, max_size=20)
|
|
174
|
+
mock_pool.assert_called_once_with(
|
|
175
|
+
"dsn",
|
|
176
|
+
kwargs={"autocommit": True},
|
|
177
|
+
min_size=2,
|
|
178
|
+
max_size=20,
|
|
179
|
+
open=False,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class TestAsyncPostgresConnectorContextManager:
|
|
184
|
+
@pytest.mark.asyncio
|
|
185
|
+
async def test_aenter_opens_pool(self, async_pg):
|
|
186
|
+
async_pg.connection_pool.open = AsyncMock()
|
|
187
|
+
result = await async_pg.__aenter__()
|
|
188
|
+
assert result is async_pg
|
|
189
|
+
async_pg.connection_pool.open.assert_awaited_once()
|
|
190
|
+
|
|
191
|
+
@pytest.mark.asyncio
|
|
192
|
+
async def test_aexit_closes_pool(self, async_pg):
|
|
193
|
+
async_pg.connection_pool.close = AsyncMock()
|
|
194
|
+
await async_pg.__aexit__(None, None, None)
|
|
195
|
+
async_pg.connection_pool.close.assert_awaited_once()
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class TestAsyncPostgresConnectorQuery:
|
|
199
|
+
@pytest.mark.asyncio
|
|
200
|
+
async def test_async_query_returns_results(self, async_pg):
|
|
201
|
+
mock_cursor = AsyncMock()
|
|
202
|
+
mock_cursor.fetchall.return_value = [("row1",)]
|
|
203
|
+
|
|
204
|
+
mock_conn = AsyncMock()
|
|
205
|
+
mock_conn.execute.return_value = mock_cursor
|
|
206
|
+
|
|
207
|
+
async_cm = AsyncMock()
|
|
208
|
+
async_cm.__aenter__.return_value = mock_conn
|
|
209
|
+
async_pg.connection_pool.connection.return_value = async_cm
|
|
210
|
+
|
|
211
|
+
result = await async_pg.async_query("SELECT 1")
|
|
212
|
+
assert result == [("row1",)]
|
|
213
|
+
mock_conn.execute.assert_awaited_once_with("SELECT 1", None)
|
|
214
|
+
|
|
215
|
+
@pytest.mark.asyncio
|
|
216
|
+
async def test_async_query_passes_params(self, async_pg):
|
|
217
|
+
mock_cursor = AsyncMock()
|
|
218
|
+
mock_cursor.fetchall.return_value = []
|
|
219
|
+
|
|
220
|
+
mock_conn = AsyncMock()
|
|
221
|
+
mock_conn.execute.return_value = mock_cursor
|
|
222
|
+
|
|
223
|
+
async_cm = AsyncMock()
|
|
224
|
+
async_cm.__aenter__.return_value = mock_conn
|
|
225
|
+
async_pg.connection_pool.connection.return_value = async_cm
|
|
226
|
+
|
|
227
|
+
await async_pg.async_query("SELECT * FROM t WHERE id = %s", (1,))
|
|
228
|
+
mock_conn.execute.assert_awaited_once_with("SELECT * FROM t WHERE id = %s", (1,))
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class TestAsyncPostgresConnectorBulkInsert:
|
|
232
|
+
@pytest.mark.asyncio
|
|
233
|
+
async def test_empty_data_returns_immediately(self, async_pg):
|
|
234
|
+
await async_pg.async_bulk_insert("my_table", [])
|
|
235
|
+
async_pg.connection_pool.connection.assert_not_called()
|
|
236
|
+
|
|
237
|
+
@pytest.mark.asyncio
|
|
238
|
+
async def test_async_bulk_insert_calls_copy(self, async_pg):
|
|
239
|
+
mock_copy = AsyncMock()
|
|
240
|
+
|
|
241
|
+
mock_cursor = MagicMock()
|
|
242
|
+
mock_cursor.copy.return_value.__aenter__ = AsyncMock(return_value=mock_copy)
|
|
243
|
+
mock_cursor.copy.return_value.__aexit__ = AsyncMock(return_value=False)
|
|
244
|
+
|
|
245
|
+
mock_conn = MagicMock()
|
|
246
|
+
mock_conn.cursor.return_value.__aenter__ = AsyncMock(return_value=mock_cursor)
|
|
247
|
+
mock_conn.cursor.return_value.__aexit__ = AsyncMock(return_value=False)
|
|
248
|
+
|
|
249
|
+
async_cm = AsyncMock()
|
|
250
|
+
async_cm.__aenter__.return_value = mock_conn
|
|
251
|
+
async_pg.connection_pool.connection.return_value = async_cm
|
|
252
|
+
|
|
253
|
+
data = [{"name": "alice", "age": 30}, {"name": "bob", "age": 25}]
|
|
254
|
+
await async_pg.async_bulk_insert("users", data)
|
|
255
|
+
|
|
256
|
+
mock_cursor.copy.assert_called_once_with("COPY users (name, age) FROM STDIN")
|
|
257
|
+
assert mock_copy.write_row.await_count == 2
|
|
258
|
+
mock_copy.write_row.assert_any_await(("alice", 30))
|
|
259
|
+
mock_copy.write_row.assert_any_await(("bob", 25))
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import httpx
|
|
2
|
-
import pytest
|
|
3
|
-
from pyapiary.api_connectors.ipqs import IPQSConnector
|
|
4
|
-
|
|
5
|
-
@pytest.mark.integration
|
|
6
|
-
def test_ipqs_malicious_url_vcr(vcr_cassette):
|
|
7
|
-
with vcr_cassette.use_cassette("test_ipqs_malicious_url_vcr"):
|
|
8
|
-
connector = IPQSConnector(load_env_vars=True, enable_logging=True)
|
|
9
|
-
result = connector.malicious_url("github.com")
|
|
10
|
-
|
|
11
|
-
assert isinstance(result, httpx.Response)
|
|
12
|
-
assert "domain" in result.json()
|
|
13
|
-
assert result.json()["domain"] == "github.com"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyapiary-2.1.4/src/pyapiary/tests/test_ipqs → pyapiary-2.2.0/src/pyapiary/tests}/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_domaintools/test_unit_async_domaintools.py
RENAMED
|
File without changes
|
{pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_domaintools/test_unit_domaintools.py
RENAMED
|
File without changes
|
{pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_elasticsearch/test_unit_elasticsearch.py
RENAMED
|
File without changes
|
|
File without changes
|
{pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_flashpoint/test_integration_flashpoint.py
RENAMED
|
File without changes
|
{pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_flashpoint/test_unit_async_flashpoint.py
RENAMED
|
File without changes
|
{pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_flashpoint/test_unit_flashpoint.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_generic/test_unit_generic_connector.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_spycloud/test_integration_spycloud.py
RENAMED
|
File without changes
|
{pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_spycloud/test_unit_async_spycloud.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_urlscan/test_integration_urlscan.py
RENAMED
|
File without changes
|
{pyapiary-2.1.4 → pyapiary-2.2.0}/src/pyapiary/tests/test_urlscan/test_unit_async_urlscan.py
RENAMED
|
File without changes
|
|
File without changes
|