pyapiary 2.2.0__tar.gz → 2.2.1__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.2.0 → pyapiary-2.2.1}/PKG-INFO +1 -1
- {pyapiary-2.2.0 → pyapiary-2.2.1}/pyproject.toml +1 -1
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/dbms_connectors/postgres.py +10 -3
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_postgres/test_unit_postgres.py +73 -43
- {pyapiary-2.2.0 → pyapiary-2.2.1}/README.md +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/__init__.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/api_connectors/__init__.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/api_connectors/broker.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/api_connectors/domaintools.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/api_connectors/flashpoint.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/api_connectors/generic.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/api_connectors/ipqs.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/api_connectors/spycloud.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/api_connectors/twilio.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/api_connectors/urlscan.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/dbms_connectors/__init__.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/dbms_connectors/elasticsearch.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/dbms_connectors/mongo.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/dbms_connectors/mongo_async.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/dbms_connectors/odbc.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/dbms_connectors/splunk.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/dbms_connectors/trino.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/helpers.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/__init__.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/conftest.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_broker/test_integration_broker.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_broker/test_unit_asyncbroker.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_broker/test_unit_broker.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_domaintools/cassettes/.gitkeep +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_domaintools/cassettes/test_domaintools_iris_investigate_vcr.yaml +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_domaintools/cassettes/test_domaintools_parsed_whois_vcr.yaml +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_domaintools/test_integration_domaintools.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_domaintools/test_unit_async_domaintools.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_domaintools/test_unit_domaintools.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_elasticsearch/test_unit_elasticsearch.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_flashpoint/cassettes/test_flashpoint_search_fraud_vcr.yaml +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_flashpoint/test_integration_flashpoint.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_flashpoint/test_unit_async_flashpoint.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_flashpoint/test_unit_flashpoint.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_generic/cassettes/test_generic_get_github_api.yaml +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_generic/test_integration_generic_connector.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_generic/test_unit_async_generic_connector.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_generic/test_unit_generic_connector.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_ipqs/__init__.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_ipqs/cassettes/test_ipqs_malicious_url_vcr.yaml +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_ipqs/cassettes/test_ipqs_phone_validation_vcr.yaml +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_ipqs/test_integration_ipqs.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_ipqs/test_unit_async_ipqs.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_ipqs/test_unit_ipqs.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_mongodb/test_unit_async_mongo.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_mongodb/test_unit_mongo.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_odbc/test_unit_odbc.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_splunk/test_unit_splunk.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_spycloud/cassettes/test_spycloud_ato_search_vcr.yaml +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_spycloud/test_integration_spycloud.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_spycloud/test_unit_async_spycloud.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_spycloud/test_unit_spycloud.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_twilio/cassettes/test_lookup_phone_vcr.yaml +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_twilio/test_integration_twilio.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_twilio/test_unit_async_twilio.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_twilio/test_unit_twilio.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_urlscan/cassettes/test_urlscan_results_vcr.yaml +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_urlscan/test_integration_urlscan.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_urlscan/test_unit_async_urlscan.py +0 -0
- {pyapiary-2.2.0 → pyapiary-2.2.1}/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.2.
|
|
4
|
+
version = "2.2.1"
|
|
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"
|
|
@@ -35,9 +35,13 @@ class PostgresConnector:
|
|
|
35
35
|
https://www.psycopg.org/psycopg3/docs/api/pool.html#module-psycopg_pool
|
|
36
36
|
"""
|
|
37
37
|
with self.connection_pool.connection() as conn:
|
|
38
|
-
with conn.
|
|
38
|
+
with conn.cursor() as cur:
|
|
39
39
|
# claude recommended a transaction wrapper here
|
|
40
|
-
|
|
40
|
+
cur.execute(query, params)
|
|
41
|
+
if cur.description:
|
|
42
|
+
return cur.fetchall()
|
|
43
|
+
else:
|
|
44
|
+
return None
|
|
41
45
|
|
|
42
46
|
def bulk_insert(self, table: str, data: List[Dict[str, Any]]):
|
|
43
47
|
if not data:
|
|
@@ -82,7 +86,10 @@ class AsyncPostgresConnector:
|
|
|
82
86
|
"""
|
|
83
87
|
async with self.connection_pool.connection() as conn:
|
|
84
88
|
cur = await conn.execute(query, params)
|
|
85
|
-
|
|
89
|
+
if cur.description:
|
|
90
|
+
return await cur.fetchall()
|
|
91
|
+
else:
|
|
92
|
+
return None
|
|
86
93
|
|
|
87
94
|
async def async_bulk_insert(self, table_name: str, data: List[Dict[str, Any]]):
|
|
88
95
|
if not data:
|
|
@@ -3,13 +3,26 @@ from unittest.mock import MagicMock, AsyncMock, patch
|
|
|
3
3
|
|
|
4
4
|
import pytest
|
|
5
5
|
|
|
6
|
-
# Mock psycopg_pool before importing the module so tests run even when the
|
|
7
|
-
# driver is not installed in the environment.
|
|
8
6
|
sys.modules.setdefault("psycopg_pool", MagicMock())
|
|
9
7
|
|
|
10
8
|
from pyapiary.dbms_connectors.postgres import PostgresConnector, AsyncPostgresConnector
|
|
11
9
|
|
|
12
10
|
|
|
11
|
+
# ──────────────────────────────────────────────
|
|
12
|
+
# Helpers
|
|
13
|
+
# ──────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
def make_sync_conn(cursor):
|
|
16
|
+
mock_conn = MagicMock()
|
|
17
|
+
mock_conn.cursor.return_value.__enter__ = MagicMock(return_value=cursor)
|
|
18
|
+
mock_conn.cursor.return_value.__exit__ = MagicMock(return_value=False)
|
|
19
|
+
return mock_conn
|
|
20
|
+
|
|
21
|
+
def wire_pool(pg, conn):
|
|
22
|
+
pg.connection_pool.connection.return_value.__enter__ = MagicMock(return_value=conn)
|
|
23
|
+
pg.connection_pool.connection.return_value.__exit__ = MagicMock(return_value=False)
|
|
24
|
+
|
|
25
|
+
|
|
13
26
|
# ──────────────────────────────────────────────
|
|
14
27
|
# Sync PostgresConnector
|
|
15
28
|
# ──────────────────────────────────────────────
|
|
@@ -76,28 +89,42 @@ class TestPostgresConnectorClose:
|
|
|
76
89
|
|
|
77
90
|
|
|
78
91
|
class TestPostgresConnectorQuery:
|
|
79
|
-
def
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
pg
|
|
84
|
-
mock_conn.transaction.return_value.__enter__ = MagicMock()
|
|
85
|
-
mock_conn.transaction.return_value.__exit__ = MagicMock(return_value=False)
|
|
92
|
+
def test_select_returns_rows(self, pg):
|
|
93
|
+
cur = MagicMock()
|
|
94
|
+
cur.description = [("col1",)]
|
|
95
|
+
cur.fetchall.return_value = [("row1",), ("row2",)]
|
|
96
|
+
wire_pool(pg, make_sync_conn(cur))
|
|
86
97
|
|
|
87
98
|
result = pg.query("SELECT 1")
|
|
88
99
|
assert result == [("row1",), ("row2",)]
|
|
89
|
-
|
|
100
|
+
cur.execute.assert_called_once_with("SELECT 1", None)
|
|
90
101
|
|
|
91
|
-
def
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
pg
|
|
96
|
-
mock_conn.transaction.return_value.__enter__ = MagicMock()
|
|
97
|
-
mock_conn.transaction.return_value.__exit__ = MagicMock(return_value=False)
|
|
102
|
+
def test_select_passes_params(self, pg):
|
|
103
|
+
cur = MagicMock()
|
|
104
|
+
cur.description = [("col1",)]
|
|
105
|
+
cur.fetchall.return_value = []
|
|
106
|
+
wire_pool(pg, make_sync_conn(cur))
|
|
98
107
|
|
|
99
108
|
pg.query("SELECT * FROM t WHERE id = %s", (42,))
|
|
100
|
-
|
|
109
|
+
cur.execute.assert_called_once_with("SELECT * FROM t WHERE id = %s", (42,))
|
|
110
|
+
|
|
111
|
+
def test_non_select_returns_none(self, pg):
|
|
112
|
+
cur = MagicMock()
|
|
113
|
+
cur.description = None # INSERT/DDL has no description
|
|
114
|
+
wire_pool(pg, make_sync_conn(cur))
|
|
115
|
+
|
|
116
|
+
result = pg.query("INSERT INTO t VALUES (1)")
|
|
117
|
+
assert result is None
|
|
118
|
+
cur.fetchall.assert_not_called()
|
|
119
|
+
|
|
120
|
+
def test_select_empty_result_returns_empty_list(self, pg):
|
|
121
|
+
cur = MagicMock()
|
|
122
|
+
cur.description = [("col1",)]
|
|
123
|
+
cur.fetchall.return_value = []
|
|
124
|
+
wire_pool(pg, make_sync_conn(cur))
|
|
125
|
+
|
|
126
|
+
result = pg.query("SELECT 1 WHERE false")
|
|
127
|
+
assert result == []
|
|
101
128
|
|
|
102
129
|
|
|
103
130
|
class TestPostgresConnectorBulkInsert:
|
|
@@ -111,12 +138,7 @@ class TestPostgresConnectorBulkInsert:
|
|
|
111
138
|
mock_cursor.copy.return_value.__enter__ = MagicMock(return_value=mock_copy)
|
|
112
139
|
mock_cursor.copy.return_value.__exit__ = MagicMock(return_value=False)
|
|
113
140
|
|
|
114
|
-
|
|
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)
|
|
141
|
+
wire_pool(pg, make_sync_conn(mock_cursor))
|
|
120
142
|
|
|
121
143
|
data = [{"name": "alice", "age": 30}, {"name": "bob", "age": 25}]
|
|
122
144
|
pg.bulk_insert("users", data)
|
|
@@ -196,36 +218,44 @@ class TestAsyncPostgresConnectorContextManager:
|
|
|
196
218
|
|
|
197
219
|
|
|
198
220
|
class TestAsyncPostgresConnectorQuery:
|
|
199
|
-
|
|
200
|
-
async def test_async_query_returns_results(self, async_pg):
|
|
201
|
-
mock_cursor = AsyncMock()
|
|
202
|
-
mock_cursor.fetchall.return_value = [("row1",)]
|
|
203
|
-
|
|
221
|
+
def _wire(self, async_pg, cur):
|
|
204
222
|
mock_conn = AsyncMock()
|
|
205
|
-
mock_conn.execute
|
|
206
|
-
|
|
223
|
+
mock_conn.execute = AsyncMock(return_value=cur)
|
|
207
224
|
async_cm = AsyncMock()
|
|
208
225
|
async_cm.__aenter__.return_value = mock_conn
|
|
209
226
|
async_pg.connection_pool.connection.return_value = async_cm
|
|
227
|
+
return mock_conn
|
|
228
|
+
|
|
229
|
+
@pytest.mark.asyncio
|
|
230
|
+
async def test_select_returns_rows(self, async_pg):
|
|
231
|
+
cur = AsyncMock()
|
|
232
|
+
cur.description = [("col1",)]
|
|
233
|
+
cur.fetchall = AsyncMock(return_value=[("row1",)])
|
|
234
|
+
conn = self._wire(async_pg, cur)
|
|
210
235
|
|
|
211
236
|
result = await async_pg.async_query("SELECT 1")
|
|
212
237
|
assert result == [("row1",)]
|
|
213
|
-
|
|
238
|
+
conn.execute.assert_awaited_once_with("SELECT 1", None)
|
|
214
239
|
|
|
215
240
|
@pytest.mark.asyncio
|
|
216
|
-
async def
|
|
217
|
-
|
|
218
|
-
|
|
241
|
+
async def test_select_passes_params(self, async_pg):
|
|
242
|
+
cur = AsyncMock()
|
|
243
|
+
cur.description = [("col1",)]
|
|
244
|
+
cur.fetchall = AsyncMock(return_value=[])
|
|
245
|
+
conn = self._wire(async_pg, cur)
|
|
219
246
|
|
|
220
|
-
|
|
221
|
-
|
|
247
|
+
await async_pg.async_query("SELECT * FROM t WHERE id = %s", (1,))
|
|
248
|
+
conn.execute.assert_awaited_once_with("SELECT * FROM t WHERE id = %s", (1,))
|
|
222
249
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
250
|
+
@pytest.mark.asyncio
|
|
251
|
+
async def test_non_select_returns_none(self, async_pg):
|
|
252
|
+
cur = AsyncMock()
|
|
253
|
+
cur.description = None
|
|
254
|
+
conn = self._wire(async_pg, cur)
|
|
226
255
|
|
|
227
|
-
await async_pg.async_query("
|
|
228
|
-
|
|
256
|
+
result = await async_pg.async_query("INSERT INTO t VALUES (1)")
|
|
257
|
+
assert result is None
|
|
258
|
+
cur.fetchall.assert_not_called()
|
|
229
259
|
|
|
230
260
|
|
|
231
261
|
class TestAsyncPostgresConnectorBulkInsert:
|
|
@@ -256,4 +286,4 @@ class TestAsyncPostgresConnectorBulkInsert:
|
|
|
256
286
|
mock_cursor.copy.assert_called_once_with("COPY users (name, age) FROM STDIN")
|
|
257
287
|
assert mock_copy.write_row.await_count == 2
|
|
258
288
|
mock_copy.write_row.assert_any_await(("alice", 30))
|
|
259
|
-
mock_copy.write_row.assert_any_await(("bob", 25))
|
|
289
|
+
mock_copy.write_row.assert_any_await(("bob", 25))
|
|
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
|
|
File without changes
|
{pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_domaintools/test_unit_async_domaintools.py
RENAMED
|
File without changes
|
{pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_domaintools/test_unit_domaintools.py
RENAMED
|
File without changes
|
{pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_elasticsearch/test_unit_elasticsearch.py
RENAMED
|
File without changes
|
|
File without changes
|
{pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_flashpoint/test_integration_flashpoint.py
RENAMED
|
File without changes
|
{pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_flashpoint/test_unit_async_flashpoint.py
RENAMED
|
File without changes
|
{pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_flashpoint/test_unit_flashpoint.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyapiary-2.2.0 → pyapiary-2.2.1}/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
|
{pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_spycloud/test_integration_spycloud.py
RENAMED
|
File without changes
|
{pyapiary-2.2.0 → pyapiary-2.2.1}/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.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_urlscan/test_integration_urlscan.py
RENAMED
|
File without changes
|
{pyapiary-2.2.0 → pyapiary-2.2.1}/src/pyapiary/tests/test_urlscan/test_unit_async_urlscan.py
RENAMED
|
File without changes
|
|
File without changes
|