pyapiary 2.3.0__tar.gz → 2.3.2__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.3.0 → pyapiary-2.3.2}/PKG-INFO +1 -1
- {pyapiary-2.3.0 → pyapiary-2.3.2}/pyproject.toml +1 -1
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/api_connectors/flashpoint.py +20 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/dbms_connectors/trino.py +2 -6
- pyapiary-2.3.2/src/pyapiary/tests/test_flashpoint/test_unit_async_flashpoint.py +100 -0
- pyapiary-2.3.2/src/pyapiary/tests/test_flashpoint/test_unit_flashpoint.py +94 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_trino/test_unit_trino.py +46 -33
- pyapiary-2.3.0/src/pyapiary/tests/test_flashpoint/test_unit_async_flashpoint.py +0 -49
- pyapiary-2.3.0/src/pyapiary/tests/test_flashpoint/test_unit_flashpoint.py +0 -45
- {pyapiary-2.3.0 → pyapiary-2.3.2}/README.md +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/__init__.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/api_connectors/__init__.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/api_connectors/broker.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/api_connectors/domaintools.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/api_connectors/generic.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/api_connectors/ipqs.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/api_connectors/spycloud.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/api_connectors/twilio.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/api_connectors/urlscan.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/dbms_connectors/__init__.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/dbms_connectors/elasticsearch.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/dbms_connectors/mongo.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/dbms_connectors/mongo_async.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/dbms_connectors/odbc.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/dbms_connectors/postgres.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/dbms_connectors/splunk.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/helpers.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/__init__.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/conftest.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_broker/test_integration_broker.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_broker/test_unit_asyncbroker.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_broker/test_unit_broker.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_domaintools/cassettes/.gitkeep +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_domaintools/cassettes/test_domaintools_iris_investigate_vcr.yaml +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_domaintools/cassettes/test_domaintools_parsed_whois_vcr.yaml +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_domaintools/test_integration_domaintools.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_domaintools/test_unit_async_domaintools.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_domaintools/test_unit_domaintools.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_elasticsearch/test_unit_elasticsearch.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_flashpoint/cassettes/test_flashpoint_search_fraud_vcr.yaml +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_flashpoint/test_integration_flashpoint.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_generic/cassettes/test_generic_get_github_api.yaml +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_generic/test_integration_generic_connector.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_generic/test_unit_async_generic_connector.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_generic/test_unit_generic_connector.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_ipqs/__init__.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_ipqs/cassettes/test_ipqs_malicious_url_vcr.yaml +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_ipqs/cassettes/test_ipqs_phone_validation_vcr.yaml +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_ipqs/test_integration_ipqs.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_ipqs/test_unit_async_ipqs.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_ipqs/test_unit_ipqs.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_mongodb/test_unit_async_mongo.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_mongodb/test_unit_mongo.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_odbc/test_unit_odbc.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_postgres/test_unit_postgres.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_splunk/test_unit_splunk.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_spycloud/cassettes/test_spycloud_ato_search_vcr.yaml +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_spycloud/test_integration_spycloud.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_spycloud/test_unit_async_spycloud.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_spycloud/test_unit_spycloud.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_twilio/cassettes/test_lookup_phone_vcr.yaml +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_twilio/test_integration_twilio.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_twilio/test_unit_async_twilio.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_twilio/test_unit_twilio.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_urlscan/cassettes/test_urlscan_results_vcr.yaml +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_urlscan/test_integration_urlscan.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_urlscan/test_unit_async_urlscan.py +0 -0
- {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_urlscan/test_unit_urlscan.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "pyapiary"
|
|
3
3
|
packages = [{ include = "pyapiary", from = "src" }]
|
|
4
|
-
version = "2.3.
|
|
4
|
+
version = "2.3.2"
|
|
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"
|
|
@@ -44,6 +44,16 @@ class FlashpointConnector(Broker):
|
|
|
44
44
|
"""
|
|
45
45
|
return self.post("/sources/v2/fraud", json={"query": query, **kwargs})
|
|
46
46
|
|
|
47
|
+
@log_method_call
|
|
48
|
+
def search_fraud_checks(self, query: str, **kwargs) -> httpx.Response:
|
|
49
|
+
"""Checks search provides the ability to search and read data from our Checks dataset.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
query (str): The search string used in the API query.
|
|
53
|
+
**kwargs: Additional query logic per the Flashpoint API documentation.
|
|
54
|
+
"""
|
|
55
|
+
return self.post("/sources/v2/fraud/checks", json={"query": query, **kwargs})
|
|
56
|
+
|
|
47
57
|
@log_method_call
|
|
48
58
|
def search_marketplaces(self, query: str, **kwargs) -> httpx.Response:
|
|
49
59
|
"""
|
|
@@ -140,6 +150,16 @@ class AsyncFlashpointConnector(AsyncBroker):
|
|
|
140
150
|
"""
|
|
141
151
|
return await self.post("/sources/v2/fraud", json={"query": query, **kwargs})
|
|
142
152
|
|
|
153
|
+
@log_method_call
|
|
154
|
+
async def search_fraud_checks(self, query: str, **kwargs) -> httpx.Response:
|
|
155
|
+
"""Checks search provides the ability to search and read data from our Checks dataset.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
query (str): The search string used in the API query.
|
|
159
|
+
**kwargs: Additional query logic per the Flashpoint API documentation.
|
|
160
|
+
"""
|
|
161
|
+
return await self.post("/sources/v2/fraud/checks", json={"query": query, **kwargs})
|
|
162
|
+
|
|
143
163
|
@log_method_call
|
|
144
164
|
async def search_marketplaces(self, query: str, **kwargs) -> httpx.Response:
|
|
145
165
|
"""
|
|
@@ -2,13 +2,9 @@ from trino.dbapi import connect
|
|
|
2
2
|
from typing import List, Dict, Any
|
|
3
3
|
|
|
4
4
|
class TrinoConnector:
|
|
5
|
-
def __init__(self,
|
|
5
|
+
def __init__(self, **kwargs):
|
|
6
6
|
self.conn = connect(
|
|
7
|
-
|
|
8
|
-
port=port,
|
|
9
|
-
user=user,
|
|
10
|
-
catalog=catalog,
|
|
11
|
-
schema=schema
|
|
7
|
+
**kwargs
|
|
12
8
|
)
|
|
13
9
|
|
|
14
10
|
def query(self, query_str):
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import httpx
|
|
3
|
+
from unittest.mock import patch, AsyncMock
|
|
4
|
+
from pyapiary.api_connectors.flashpoint import AsyncFlashpointConnector
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.mark.asyncio
|
|
8
|
+
async def test_async_init_with_api_key():
|
|
9
|
+
connector = AsyncFlashpointConnector(api_key="test_token")
|
|
10
|
+
assert connector.api_key == "test_token"
|
|
11
|
+
assert connector.headers["Authorization"] == "Bearer test_token"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@patch.dict("os.environ", {"FLASHPOINT_API_KEY": "env_token"}, clear=True)
|
|
15
|
+
@pytest.mark.asyncio
|
|
16
|
+
async def test_async_init_with_env_key():
|
|
17
|
+
connector = AsyncFlashpointConnector(load_env_vars=True)
|
|
18
|
+
assert connector.api_key == "env_token"
|
|
19
|
+
assert connector.headers["Authorization"] == "Bearer env_token"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.mark.asyncio
|
|
23
|
+
async def test_async_init_missing_key():
|
|
24
|
+
with patch.dict("os.environ", {}, clear=True):
|
|
25
|
+
with pytest.raises(ValueError, match="FLASHPOINT_API_KEY is required"):
|
|
26
|
+
AsyncFlashpointConnector()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@patch("pyapiary.api_connectors.flashpoint.AsyncFlashpointConnector.post", new_callable=AsyncMock)
|
|
30
|
+
@pytest.mark.asyncio
|
|
31
|
+
async def test_async_search_fraud_checks(mock_post):
|
|
32
|
+
import json
|
|
33
|
+
|
|
34
|
+
request = httpx.Request("POST", "https://api.flashpoint.io/mock")
|
|
35
|
+
payload = {"success": True, "data": []}
|
|
36
|
+
mock_response = httpx.Response(
|
|
37
|
+
200,
|
|
38
|
+
request=request,
|
|
39
|
+
content=json.dumps(payload).encode("utf-8"),
|
|
40
|
+
headers={"Content-Type": "application/json"},
|
|
41
|
+
)
|
|
42
|
+
mock_post.return_value = mock_response
|
|
43
|
+
|
|
44
|
+
connector = AsyncFlashpointConnector(api_key="mock_token")
|
|
45
|
+
result = await connector.search_fraud_checks("stolen checks")
|
|
46
|
+
|
|
47
|
+
assert isinstance(result, httpx.Response)
|
|
48
|
+
assert result.json() == payload
|
|
49
|
+
mock_post.assert_awaited_once_with(
|
|
50
|
+
"/sources/v2/fraud/checks", json={"query": "stolen checks"}
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@patch("pyapiary.api_connectors.flashpoint.AsyncFlashpointConnector.post", new_callable=AsyncMock)
|
|
55
|
+
@pytest.mark.asyncio
|
|
56
|
+
async def test_async_search_fraud_checks_with_kwargs(mock_post):
|
|
57
|
+
import json
|
|
58
|
+
|
|
59
|
+
request = httpx.Request("POST", "https://api.flashpoint.io/mock")
|
|
60
|
+
payload = {"success": True, "data": [{"id": "abc123"}]}
|
|
61
|
+
mock_response = httpx.Response(
|
|
62
|
+
200,
|
|
63
|
+
request=request,
|
|
64
|
+
content=json.dumps(payload).encode("utf-8"),
|
|
65
|
+
headers={"Content-Type": "application/json"},
|
|
66
|
+
)
|
|
67
|
+
mock_post.return_value = mock_response
|
|
68
|
+
|
|
69
|
+
connector = AsyncFlashpointConnector(api_key="mock_token")
|
|
70
|
+
result = await connector.search_fraud_checks("routing number", size=10, from_=0)
|
|
71
|
+
|
|
72
|
+
assert isinstance(result, httpx.Response)
|
|
73
|
+
assert result.json() == payload
|
|
74
|
+
mock_post.assert_awaited_once_with(
|
|
75
|
+
"/sources/v2/fraud/checks",
|
|
76
|
+
json={"query": "routing number", "size": 10, "from_": 0},
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@patch("pyapiary.api_connectors.flashpoint.AsyncFlashpointConnector.post", new_callable=AsyncMock)
|
|
81
|
+
@pytest.mark.asyncio
|
|
82
|
+
async def test_async_search_fraud(mock_post):
|
|
83
|
+
import json
|
|
84
|
+
|
|
85
|
+
request = httpx.Request("POST", "https://api.flashpoint.io/mock")
|
|
86
|
+
payload = {"success": True, "data": []}
|
|
87
|
+
mock_response = httpx.Response(
|
|
88
|
+
200,
|
|
89
|
+
request=request,
|
|
90
|
+
content=json.dumps(payload).encode("utf-8"),
|
|
91
|
+
headers={"Content-Type": "application/json"},
|
|
92
|
+
)
|
|
93
|
+
mock_post.return_value = mock_response
|
|
94
|
+
|
|
95
|
+
connector = AsyncFlashpointConnector(api_key="mock_token")
|
|
96
|
+
result = await connector.search_fraud("credential stuffing")
|
|
97
|
+
|
|
98
|
+
assert isinstance(result, httpx.Response)
|
|
99
|
+
assert result.json() == payload
|
|
100
|
+
mock_post.assert_awaited_once()
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
import pytest
|
|
3
|
+
from unittest.mock import patch, MagicMock
|
|
4
|
+
from pyapiary.api_connectors.flashpoint import FlashpointConnector
|
|
5
|
+
|
|
6
|
+
def test_init_with_api_key():
|
|
7
|
+
connector = FlashpointConnector(api_key="test_token")
|
|
8
|
+
assert connector.api_key == "test_token"
|
|
9
|
+
assert connector.headers["Authorization"] == "Bearer test_token"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@patch.dict("os.environ", {"FLASHPOINT_API_KEY": "env_token"}, clear=True)
|
|
13
|
+
def test_init_with_env_key():
|
|
14
|
+
connector = FlashpointConnector(load_env_vars=True)
|
|
15
|
+
assert connector.api_key == "env_token"
|
|
16
|
+
assert connector.headers["Authorization"] == "Bearer env_token"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_init_missing_key():
|
|
20
|
+
with patch.dict("os.environ", {}, clear=True):
|
|
21
|
+
with pytest.raises(ValueError, match="FLASHPOINT_API_KEY is required"):
|
|
22
|
+
FlashpointConnector()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@patch("pyapiary.api_connectors.flashpoint.FlashpointConnector.post")
|
|
26
|
+
def test_search_fraud_checks(mock_post):
|
|
27
|
+
import json
|
|
28
|
+
|
|
29
|
+
request = httpx.Request("POST", "https://api.flashpoint.io/mock")
|
|
30
|
+
payload = {"success": True, "data": []}
|
|
31
|
+
mock_response = httpx.Response(
|
|
32
|
+
200,
|
|
33
|
+
request=request,
|
|
34
|
+
content=json.dumps(payload).encode("utf-8"),
|
|
35
|
+
headers={"Content-Type": "application/json"},
|
|
36
|
+
)
|
|
37
|
+
mock_post.return_value = mock_response
|
|
38
|
+
|
|
39
|
+
connector = FlashpointConnector(api_key="mock_token")
|
|
40
|
+
result = connector.search_fraud_checks("stolen checks")
|
|
41
|
+
|
|
42
|
+
assert isinstance(result, httpx.Response)
|
|
43
|
+
assert result.json() == payload
|
|
44
|
+
mock_post.assert_called_once_with(
|
|
45
|
+
"/sources/v2/fraud/checks", json={"query": "stolen checks"}
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@patch("pyapiary.api_connectors.flashpoint.FlashpointConnector.post")
|
|
50
|
+
def test_search_fraud_checks_with_kwargs(mock_post):
|
|
51
|
+
import json
|
|
52
|
+
|
|
53
|
+
request = httpx.Request("POST", "https://api.flashpoint.io/mock")
|
|
54
|
+
payload = {"success": True, "data": [{"id": "abc123"}]}
|
|
55
|
+
mock_response = httpx.Response(
|
|
56
|
+
200,
|
|
57
|
+
request=request,
|
|
58
|
+
content=json.dumps(payload).encode("utf-8"),
|
|
59
|
+
headers={"Content-Type": "application/json"},
|
|
60
|
+
)
|
|
61
|
+
mock_post.return_value = mock_response
|
|
62
|
+
|
|
63
|
+
connector = FlashpointConnector(api_key="mock_token")
|
|
64
|
+
result = connector.search_fraud_checks("routing number", size=10, from_=0)
|
|
65
|
+
|
|
66
|
+
assert isinstance(result, httpx.Response)
|
|
67
|
+
assert result.json() == payload
|
|
68
|
+
mock_post.assert_called_once_with(
|
|
69
|
+
"/sources/v2/fraud/checks",
|
|
70
|
+
json={"query": "routing number", "size": 10, "from_": 0},
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@patch("pyapiary.api_connectors.flashpoint.FlashpointConnector.post")
|
|
75
|
+
def test_search_fraud(mock_post):
|
|
76
|
+
import json
|
|
77
|
+
|
|
78
|
+
# Use a real httpx.Response so our test matches the new return type
|
|
79
|
+
request = httpx.Request("POST", "https://api.flashpoint.io/mock")
|
|
80
|
+
payload = {"success": True, "data": []}
|
|
81
|
+
mock_response = httpx.Response(
|
|
82
|
+
200,
|
|
83
|
+
request=request,
|
|
84
|
+
content=json.dumps(payload).encode("utf-8"),
|
|
85
|
+
headers={"Content-Type": "application/json"},
|
|
86
|
+
)
|
|
87
|
+
mock_post.return_value = mock_response
|
|
88
|
+
|
|
89
|
+
connector = FlashpointConnector(api_key="mock_token")
|
|
90
|
+
result = connector.search_fraud("credential stuffing")
|
|
91
|
+
|
|
92
|
+
assert isinstance(result, httpx.Response)
|
|
93
|
+
assert result.json() == payload
|
|
94
|
+
mock_post.assert_called_once()
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import pytest
|
|
2
|
-
from unittest.mock import MagicMock, patch
|
|
2
|
+
from unittest.mock import MagicMock, patch
|
|
3
3
|
from pyapiary.dbms_connectors.trino import TrinoConnector
|
|
4
4
|
|
|
5
5
|
|
|
@@ -15,11 +15,12 @@ def mock_connect(mocker):
|
|
|
15
15
|
def connector(mock_connect):
|
|
16
16
|
"""Return a TrinoConnector backed by a mocked connection."""
|
|
17
17
|
return TrinoConnector(
|
|
18
|
-
host="
|
|
19
|
-
port=
|
|
18
|
+
host="trino.trashcollector.dev",
|
|
19
|
+
port=443,
|
|
20
20
|
user="test_user",
|
|
21
21
|
catalog="hive",
|
|
22
22
|
schema="default",
|
|
23
|
+
http_scheme="https",
|
|
23
24
|
)
|
|
24
25
|
|
|
25
26
|
|
|
@@ -28,31 +29,33 @@ def connector(mock_connect):
|
|
|
28
29
|
# ---------------------------------------------------------------------------
|
|
29
30
|
|
|
30
31
|
class TestInit:
|
|
31
|
-
def
|
|
32
|
+
def test_connect_called_with_provided_kwargs(self, mocker):
|
|
32
33
|
mock_connect = mocker.patch("pyapiary.dbms_connectors.trino.connect")
|
|
33
|
-
TrinoConnector(host="myhost", port=
|
|
34
|
+
TrinoConnector(host="myhost", port=443, user="alice", http_scheme="https")
|
|
34
35
|
mock_connect.assert_called_once_with(
|
|
35
36
|
host="myhost",
|
|
36
|
-
port=
|
|
37
|
+
port=443,
|
|
37
38
|
user="alice",
|
|
38
|
-
|
|
39
|
-
schema="raw",
|
|
39
|
+
http_scheme="https",
|
|
40
40
|
)
|
|
41
41
|
|
|
42
|
-
def
|
|
42
|
+
def test_connect_called_with_minimal_kwargs(self, mocker):
|
|
43
43
|
mock_connect = mocker.patch("pyapiary.dbms_connectors.trino.connect")
|
|
44
|
-
TrinoConnector(host="myhost", port=
|
|
45
|
-
mock_connect.assert_called_once_with(
|
|
46
|
-
host="myhost",
|
|
47
|
-
port=9090,
|
|
48
|
-
user="alice",
|
|
49
|
-
catalog=None,
|
|
50
|
-
schema=None,
|
|
51
|
-
)
|
|
44
|
+
TrinoConnector(host="myhost", port=8080, user="alice")
|
|
45
|
+
mock_connect.assert_called_once_with(host="myhost", port=8080, user="alice")
|
|
52
46
|
|
|
53
47
|
def test_conn_attribute_set(self, mock_connect, connector):
|
|
54
48
|
assert connector.conn is mock_connect
|
|
55
49
|
|
|
50
|
+
def test_arbitrary_kwargs_forwarded(self, mocker):
|
|
51
|
+
"""Any kwarg the trino client supports should be forwarded as-is."""
|
|
52
|
+
mock_connect = mocker.patch("pyapiary.dbms_connectors.trino.connect")
|
|
53
|
+
TrinoConnector(host="h", port=443, user="u", http_scheme="https",
|
|
54
|
+
verify=False, session_properties={"query_max_run_time": "1h"})
|
|
55
|
+
_, call_kwargs = mock_connect.call_args
|
|
56
|
+
assert call_kwargs["verify"] is False
|
|
57
|
+
assert call_kwargs["session_properties"] == {"query_max_run_time": "1h"}
|
|
58
|
+
|
|
56
59
|
|
|
57
60
|
# ---------------------------------------------------------------------------
|
|
58
61
|
# query()
|
|
@@ -108,7 +111,17 @@ class TestQuery:
|
|
|
108
111
|
mock_connect.cursor.return_value.__enter__.return_value = mock_cursor
|
|
109
112
|
|
|
110
113
|
with pytest.raises(RuntimeError, match="syntax error"):
|
|
111
|
-
connector.query("SELECT bad
|
|
114
|
+
connector.query("SELECT bad %%")
|
|
115
|
+
|
|
116
|
+
def test_show_catalogs(self, mock_connect, connector):
|
|
117
|
+
mock_cursor = MagicMock()
|
|
118
|
+
mock_cursor.description = [("Catalog",)]
|
|
119
|
+
mock_cursor.fetchall.return_value = [("hive",), ("iceberg",), ("tpch",)]
|
|
120
|
+
mock_connect.cursor.return_value.__enter__.return_value = mock_cursor
|
|
121
|
+
|
|
122
|
+
result = connector.query("SHOW CATALOGS")
|
|
123
|
+
|
|
124
|
+
assert result == [("hive",), ("iceberg",), ("tpch",)]
|
|
112
125
|
|
|
113
126
|
|
|
114
127
|
# ---------------------------------------------------------------------------
|
|
@@ -123,27 +136,18 @@ class TestBulkInsert:
|
|
|
123
136
|
result = connector.bulk_insert("my_table", [{"id": 1, "name": "alice"}])
|
|
124
137
|
|
|
125
138
|
expected_query = "INSERT INTO my_table (id, name) VALUES (?, ?)"
|
|
126
|
-
mock_cursor.executemany.assert_called_once_with(
|
|
127
|
-
expected_query, [(1, "alice")]
|
|
128
|
-
)
|
|
139
|
+
mock_cursor.executemany.assert_called_once_with(expected_query, [(1, "alice")])
|
|
129
140
|
assert result is True
|
|
130
141
|
|
|
131
142
|
def test_inserts_multiple_rows(self, mock_connect, connector):
|
|
132
143
|
mock_cursor = MagicMock()
|
|
133
144
|
mock_connect.cursor.return_value.__enter__.return_value = mock_cursor
|
|
134
145
|
|
|
135
|
-
data = [
|
|
136
|
-
{"id": 1, "val": "a"},
|
|
137
|
-
{"id": 2, "val": "b"},
|
|
138
|
-
{"id": 3, "val": "c"},
|
|
139
|
-
]
|
|
146
|
+
data = [{"id": 1, "val": "a"}, {"id": 2, "val": "b"}, {"id": 3, "val": "c"}]
|
|
140
147
|
result = connector.bulk_insert("my_table", data)
|
|
141
148
|
|
|
142
|
-
expected_values = [(1, "a"), (2, "b"), (3, "c")]
|
|
143
|
-
_, call_values = mock_cursor.executemany.call_args
|
|
144
|
-
# positional args
|
|
145
149
|
actual_values = mock_cursor.executemany.call_args[0][1]
|
|
146
|
-
assert actual_values ==
|
|
150
|
+
assert actual_values == [(1, "a"), (2, "b"), (3, "c")]
|
|
147
151
|
assert result is True
|
|
148
152
|
|
|
149
153
|
def test_returns_none_for_empty_list(self, mock_connect, connector, capsys):
|
|
@@ -165,10 +169,10 @@ class TestBulkInsert:
|
|
|
165
169
|
mock_cursor = MagicMock()
|
|
166
170
|
mock_connect.cursor.return_value.__enter__.return_value = mock_cursor
|
|
167
171
|
|
|
168
|
-
connector.bulk_insert("
|
|
172
|
+
connector.bulk_insert("hive.default.target_table", [{"x": 99}])
|
|
169
173
|
|
|
170
174
|
actual_query = mock_cursor.executemany.call_args[0][0]
|
|
171
|
-
assert "
|
|
175
|
+
assert "hive.default.target_table" in actual_query
|
|
172
176
|
|
|
173
177
|
def test_column_order_matches_first_row_keys(self, mock_connect, connector):
|
|
174
178
|
mock_cursor = MagicMock()
|
|
@@ -178,10 +182,19 @@ class TestBulkInsert:
|
|
|
178
182
|
connector.bulk_insert("t", data)
|
|
179
183
|
|
|
180
184
|
actual_query = mock_cursor.executemany.call_args[0][0]
|
|
181
|
-
# columns in query should match key order of first dict
|
|
182
185
|
for col in ["z", "a", "m"]:
|
|
183
186
|
assert col in actual_query
|
|
184
187
|
|
|
188
|
+
def test_placeholder_count_matches_column_count(self, mock_connect, connector):
|
|
189
|
+
mock_cursor = MagicMock()
|
|
190
|
+
mock_connect.cursor.return_value.__enter__.return_value = mock_cursor
|
|
191
|
+
|
|
192
|
+
data = [{"a": 1, "b": 2, "c": 3, "d": 4}]
|
|
193
|
+
connector.bulk_insert("t", data)
|
|
194
|
+
|
|
195
|
+
actual_query = mock_cursor.executemany.call_args[0][0]
|
|
196
|
+
assert actual_query.count("?") == 4
|
|
197
|
+
|
|
185
198
|
def test_propagates_executemany_exception(self, mock_connect, connector):
|
|
186
199
|
mock_cursor = MagicMock()
|
|
187
200
|
mock_cursor.executemany.side_effect = RuntimeError("DB write error")
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
import httpx
|
|
3
|
-
from unittest.mock import patch, AsyncMock
|
|
4
|
-
from pyapiary.api_connectors.flashpoint import AsyncFlashpointConnector
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
@pytest.mark.asyncio
|
|
8
|
-
async def test_async_init_with_api_key():
|
|
9
|
-
connector = AsyncFlashpointConnector(api_key="test_token")
|
|
10
|
-
assert connector.api_key == "test_token"
|
|
11
|
-
assert connector.headers["Authorization"] == "Bearer test_token"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
@patch.dict("os.environ", {"FLASHPOINT_API_KEY": "env_token"}, clear=True)
|
|
15
|
-
@pytest.mark.asyncio
|
|
16
|
-
async def test_async_init_with_env_key():
|
|
17
|
-
connector = AsyncFlashpointConnector(load_env_vars=True)
|
|
18
|
-
assert connector.api_key == "env_token"
|
|
19
|
-
assert connector.headers["Authorization"] == "Bearer env_token"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@pytest.mark.asyncio
|
|
23
|
-
async def test_async_init_missing_key():
|
|
24
|
-
with patch.dict("os.environ", {}, clear=True):
|
|
25
|
-
with pytest.raises(ValueError, match="FLASHPOINT_API_KEY is required"):
|
|
26
|
-
AsyncFlashpointConnector()
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
@patch("pyapiary.api_connectors.flashpoint.AsyncFlashpointConnector.post", new_callable=AsyncMock)
|
|
30
|
-
@pytest.mark.asyncio
|
|
31
|
-
async def test_async_search_fraud(mock_post):
|
|
32
|
-
import json
|
|
33
|
-
|
|
34
|
-
request = httpx.Request("POST", "https://api.flashpoint.io/mock")
|
|
35
|
-
payload = {"success": True, "data": []}
|
|
36
|
-
mock_response = httpx.Response(
|
|
37
|
-
200,
|
|
38
|
-
request=request,
|
|
39
|
-
content=json.dumps(payload).encode("utf-8"),
|
|
40
|
-
headers={"Content-Type": "application/json"},
|
|
41
|
-
)
|
|
42
|
-
mock_post.return_value = mock_response
|
|
43
|
-
|
|
44
|
-
connector = AsyncFlashpointConnector(api_key="mock_token")
|
|
45
|
-
result = await connector.search_fraud("credential stuffing")
|
|
46
|
-
|
|
47
|
-
assert isinstance(result, httpx.Response)
|
|
48
|
-
assert result.json() == payload
|
|
49
|
-
mock_post.assert_awaited_once()
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import httpx
|
|
2
|
-
import pytest
|
|
3
|
-
from unittest.mock import patch, MagicMock
|
|
4
|
-
from pyapiary.api_connectors.flashpoint import FlashpointConnector
|
|
5
|
-
|
|
6
|
-
def test_init_with_api_key():
|
|
7
|
-
connector = FlashpointConnector(api_key="test_token")
|
|
8
|
-
assert connector.api_key == "test_token"
|
|
9
|
-
assert connector.headers["Authorization"] == "Bearer test_token"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@patch.dict("os.environ", {"FLASHPOINT_API_KEY": "env_token"}, clear=True)
|
|
13
|
-
def test_init_with_env_key():
|
|
14
|
-
connector = FlashpointConnector(load_env_vars=True)
|
|
15
|
-
assert connector.api_key == "env_token"
|
|
16
|
-
assert connector.headers["Authorization"] == "Bearer env_token"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def test_init_missing_key():
|
|
20
|
-
with patch.dict("os.environ", {}, clear=True):
|
|
21
|
-
with pytest.raises(ValueError, match="FLASHPOINT_API_KEY is required"):
|
|
22
|
-
FlashpointConnector()
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@patch("pyapiary.api_connectors.flashpoint.FlashpointConnector.post")
|
|
26
|
-
def test_search_fraud(mock_post):
|
|
27
|
-
import json
|
|
28
|
-
|
|
29
|
-
# Use a real httpx.Response so our test matches the new return type
|
|
30
|
-
request = httpx.Request("POST", "https://api.flashpoint.io/mock")
|
|
31
|
-
payload = {"success": True, "data": []}
|
|
32
|
-
mock_response = httpx.Response(
|
|
33
|
-
200,
|
|
34
|
-
request=request,
|
|
35
|
-
content=json.dumps(payload).encode("utf-8"),
|
|
36
|
-
headers={"Content-Type": "application/json"},
|
|
37
|
-
)
|
|
38
|
-
mock_post.return_value = mock_response
|
|
39
|
-
|
|
40
|
-
connector = FlashpointConnector(api_key="mock_token")
|
|
41
|
-
result = connector.search_fraud("credential stuffing")
|
|
42
|
-
|
|
43
|
-
assert isinstance(result, httpx.Response)
|
|
44
|
-
assert result.json() == payload
|
|
45
|
-
mock_post.assert_called_once()
|
|
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
|
|
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.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_domaintools/test_unit_async_domaintools.py
RENAMED
|
File without changes
|
{pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_domaintools/test_unit_domaintools.py
RENAMED
|
File without changes
|
{pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_elasticsearch/test_unit_elasticsearch.py
RENAMED
|
File without changes
|
|
File without changes
|
{pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_flashpoint/test_integration_flashpoint.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyapiary-2.3.0 → pyapiary-2.3.2}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_spycloud/test_integration_spycloud.py
RENAMED
|
File without changes
|
{pyapiary-2.3.0 → pyapiary-2.3.2}/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.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_urlscan/test_integration_urlscan.py
RENAMED
|
File without changes
|
{pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_urlscan/test_unit_async_urlscan.py
RENAMED
|
File without changes
|
|
File without changes
|