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.
Files changed (68) hide show
  1. {pyapiary-2.3.0 → pyapiary-2.3.2}/PKG-INFO +1 -1
  2. {pyapiary-2.3.0 → pyapiary-2.3.2}/pyproject.toml +1 -1
  3. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/api_connectors/flashpoint.py +20 -0
  4. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/dbms_connectors/trino.py +2 -6
  5. pyapiary-2.3.2/src/pyapiary/tests/test_flashpoint/test_unit_async_flashpoint.py +100 -0
  6. pyapiary-2.3.2/src/pyapiary/tests/test_flashpoint/test_unit_flashpoint.py +94 -0
  7. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_trino/test_unit_trino.py +46 -33
  8. pyapiary-2.3.0/src/pyapiary/tests/test_flashpoint/test_unit_async_flashpoint.py +0 -49
  9. pyapiary-2.3.0/src/pyapiary/tests/test_flashpoint/test_unit_flashpoint.py +0 -45
  10. {pyapiary-2.3.0 → pyapiary-2.3.2}/README.md +0 -0
  11. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/__init__.py +0 -0
  12. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/api_connectors/__init__.py +0 -0
  13. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/api_connectors/broker.py +0 -0
  14. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/api_connectors/domaintools.py +0 -0
  15. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/api_connectors/generic.py +0 -0
  16. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/api_connectors/ipqs.py +0 -0
  17. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/api_connectors/spycloud.py +0 -0
  18. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/api_connectors/twilio.py +0 -0
  19. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/api_connectors/urlscan.py +0 -0
  20. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/dbms_connectors/__init__.py +0 -0
  21. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/dbms_connectors/elasticsearch.py +0 -0
  22. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/dbms_connectors/mongo.py +0 -0
  23. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/dbms_connectors/mongo_async.py +0 -0
  24. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/dbms_connectors/odbc.py +0 -0
  25. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/dbms_connectors/postgres.py +0 -0
  26. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/dbms_connectors/splunk.py +0 -0
  27. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/helpers.py +0 -0
  28. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/__init__.py +0 -0
  29. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/conftest.py +0 -0
  30. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_broker/test_integration_broker.py +0 -0
  31. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_broker/test_unit_asyncbroker.py +0 -0
  32. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_broker/test_unit_broker.py +0 -0
  33. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_domaintools/cassettes/.gitkeep +0 -0
  34. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_domaintools/cassettes/test_domaintools_iris_investigate_vcr.yaml +0 -0
  35. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_domaintools/cassettes/test_domaintools_parsed_whois_vcr.yaml +0 -0
  36. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_domaintools/test_integration_domaintools.py +0 -0
  37. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_domaintools/test_unit_async_domaintools.py +0 -0
  38. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_domaintools/test_unit_domaintools.py +0 -0
  39. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_elasticsearch/test_unit_elasticsearch.py +0 -0
  40. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_flashpoint/cassettes/test_flashpoint_search_fraud_vcr.yaml +0 -0
  41. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_flashpoint/test_integration_flashpoint.py +0 -0
  42. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_generic/cassettes/test_generic_get_github_api.yaml +0 -0
  43. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_generic/test_integration_generic_connector.py +0 -0
  44. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_generic/test_unit_async_generic_connector.py +0 -0
  45. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_generic/test_unit_generic_connector.py +0 -0
  46. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_ipqs/__init__.py +0 -0
  47. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_ipqs/cassettes/test_ipqs_malicious_url_vcr.yaml +0 -0
  48. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_ipqs/cassettes/test_ipqs_phone_validation_vcr.yaml +0 -0
  49. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_ipqs/test_integration_ipqs.py +0 -0
  50. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_ipqs/test_unit_async_ipqs.py +0 -0
  51. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_ipqs/test_unit_ipqs.py +0 -0
  52. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_mongodb/test_unit_async_mongo.py +0 -0
  53. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_mongodb/test_unit_mongo.py +0 -0
  54. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_odbc/test_unit_odbc.py +0 -0
  55. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_postgres/test_unit_postgres.py +0 -0
  56. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_splunk/test_unit_splunk.py +0 -0
  57. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_spycloud/cassettes/test_spycloud_ato_search_vcr.yaml +0 -0
  58. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_spycloud/test_integration_spycloud.py +0 -0
  59. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_spycloud/test_unit_async_spycloud.py +0 -0
  60. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_spycloud/test_unit_spycloud.py +0 -0
  61. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_twilio/cassettes/test_lookup_phone_vcr.yaml +0 -0
  62. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_twilio/test_integration_twilio.py +0 -0
  63. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_twilio/test_unit_async_twilio.py +0 -0
  64. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_twilio/test_unit_twilio.py +0 -0
  65. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_urlscan/cassettes/test_urlscan_results_vcr.yaml +0 -0
  66. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_urlscan/test_integration_urlscan.py +0 -0
  67. {pyapiary-2.3.0 → pyapiary-2.3.2}/src/pyapiary/tests/test_urlscan/test_unit_async_urlscan.py +0 -0
  68. {pyapiary-2.3.0 → pyapiary-2.3.2}/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.0
3
+ Version: 2.3.2
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
@@ -1,7 +1,7 @@
1
1
  [tool.poetry]
2
2
  name = "pyapiary"
3
3
  packages = [{ include = "pyapiary", from = "src" }]
4
- version = "2.3.0"
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, host, port, user, catalog=None, schema=None):
5
+ def __init__(self, **kwargs):
6
6
  self.conn = connect(
7
- host=host,
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, call
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="localhost",
19
- port=8080,
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 test_connect_called_with_correct_args(self, mocker):
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=9090, user="alice", catalog="iceberg", schema="raw")
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=9090,
37
+ port=443,
37
38
  user="alice",
38
- catalog="iceberg",
39
- schema="raw",
39
+ http_scheme="https",
40
40
  )
41
41
 
42
- def test_connect_called_without_optional_args(self, mocker):
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=9090, user="alice")
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 syntax %%")
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 == expected_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("schema.target_table", [{"x": 99}])
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 "schema.target_table" in actual_query
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