pyapiary 2.2.1__tar.gz → 2.3.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.1 → pyapiary-2.3.1}/PKG-INFO +2 -1
- {pyapiary-2.2.1 → pyapiary-2.3.1}/pyproject.toml +2 -1
- pyapiary-2.3.1/src/pyapiary/dbms_connectors/trino.py +28 -0
- pyapiary-2.3.1/src/pyapiary/tests/test_trino/test_unit_trino.py +213 -0
- pyapiary-2.2.1/src/pyapiary/dbms_connectors/trino.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/README.md +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/__init__.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/api_connectors/__init__.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/api_connectors/broker.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/api_connectors/domaintools.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/api_connectors/flashpoint.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/api_connectors/generic.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/api_connectors/ipqs.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/api_connectors/spycloud.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/api_connectors/twilio.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/api_connectors/urlscan.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/dbms_connectors/__init__.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/dbms_connectors/elasticsearch.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/dbms_connectors/mongo.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/dbms_connectors/mongo_async.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/dbms_connectors/odbc.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/dbms_connectors/postgres.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/dbms_connectors/splunk.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/helpers.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/__init__.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/conftest.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_broker/test_integration_broker.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_broker/test_unit_asyncbroker.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_broker/test_unit_broker.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_domaintools/cassettes/.gitkeep +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_domaintools/cassettes/test_domaintools_iris_investigate_vcr.yaml +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_domaintools/cassettes/test_domaintools_parsed_whois_vcr.yaml +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_domaintools/test_integration_domaintools.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_domaintools/test_unit_async_domaintools.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_domaintools/test_unit_domaintools.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_elasticsearch/test_unit_elasticsearch.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_flashpoint/cassettes/test_flashpoint_search_fraud_vcr.yaml +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_flashpoint/test_integration_flashpoint.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_flashpoint/test_unit_async_flashpoint.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_flashpoint/test_unit_flashpoint.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_generic/cassettes/test_generic_get_github_api.yaml +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_generic/test_integration_generic_connector.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_generic/test_unit_async_generic_connector.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_generic/test_unit_generic_connector.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_ipqs/__init__.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_ipqs/cassettes/test_ipqs_malicious_url_vcr.yaml +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_ipqs/cassettes/test_ipqs_phone_validation_vcr.yaml +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_ipqs/test_integration_ipqs.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_ipqs/test_unit_async_ipqs.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_ipqs/test_unit_ipqs.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_mongodb/test_unit_async_mongo.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_mongodb/test_unit_mongo.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_odbc/test_unit_odbc.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_postgres/test_unit_postgres.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_splunk/test_unit_splunk.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_spycloud/cassettes/test_spycloud_ato_search_vcr.yaml +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_spycloud/test_integration_spycloud.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_spycloud/test_unit_async_spycloud.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_spycloud/test_unit_spycloud.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_twilio/cassettes/test_lookup_phone_vcr.yaml +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_twilio/test_integration_twilio.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_twilio/test_unit_async_twilio.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_twilio/test_unit_twilio.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_urlscan/cassettes/test_urlscan_results_vcr.yaml +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_urlscan/test_integration_urlscan.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_urlscan/test_unit_async_urlscan.py +0 -0
- {pyapiary-2.2.1 → pyapiary-2.3.1}/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.3.1
|
|
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
|
|
@@ -22,6 +22,7 @@ Requires-Dist: pyodbc (>=5.3.0,<6.0.0) ; extra == "odbc"
|
|
|
22
22
|
Requires-Dist: python-dotenv (>=1.1.1,<2.0.0)
|
|
23
23
|
Requires-Dist: splunk-sdk (>=2.1.1,<3.0.0)
|
|
24
24
|
Requires-Dist: tenacity (>=9.1.2,<10.0.0)
|
|
25
|
+
Requires-Dist: trino (>=0.337.0,<0.338.0)
|
|
25
26
|
Project-URL: Homepage, https://github.com/robd518/pyapiary
|
|
26
27
|
Project-URL: Repository, https://github.com/robd518/pyapiary
|
|
27
28
|
Description-Content-Type: text/markdown
|
|
@@ -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.3.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"
|
|
@@ -20,6 +20,7 @@ pyodbc = "^5.3.0"
|
|
|
20
20
|
psycopg-pool = {extras = ["binary", "pool"], version = "^3.3.1"}
|
|
21
21
|
psycopg = {extras = ["pool"], version = "^3.3.4"}
|
|
22
22
|
psycopg-binary = "^3.3.4"
|
|
23
|
+
trino = "^0.337.0"
|
|
23
24
|
|
|
24
25
|
[tool.poetry.extras]
|
|
25
26
|
odbc = ["pyodbc"]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from trino.dbapi import connect
|
|
2
|
+
from typing import List, Dict, Any
|
|
3
|
+
|
|
4
|
+
class TrinoConnector:
|
|
5
|
+
def __init__(self, **kwargs):
|
|
6
|
+
self.conn = connect(
|
|
7
|
+
**kwargs
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
def query(self, query_str):
|
|
11
|
+
with self.conn.cursor() as cur:
|
|
12
|
+
cur.execute(query_str)
|
|
13
|
+
if cur.description:
|
|
14
|
+
return cur.fetchall()
|
|
15
|
+
else:
|
|
16
|
+
return None
|
|
17
|
+
|
|
18
|
+
def bulk_insert(self, table, data : List[Dict[str,Any]]):
|
|
19
|
+
if not len(data)>0:
|
|
20
|
+
print('Invalid List of Dict type passed. Must have more than one element.')
|
|
21
|
+
return None
|
|
22
|
+
columns = data[0].keys()
|
|
23
|
+
placeholders = ", ".join(["?"] * len(columns))
|
|
24
|
+
query = f"INSERT INTO {table} ({', '.join(columns)}) VALUES ({placeholders})"
|
|
25
|
+
values = [tuple(row[col] for col in columns) for row in data]
|
|
26
|
+
with self.conn.cursor() as cur:
|
|
27
|
+
cur.executemany(query, values)
|
|
28
|
+
return True
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from unittest.mock import MagicMock, patch
|
|
3
|
+
from pyapiary.dbms_connectors.trino import TrinoConnector
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@pytest.fixture
|
|
7
|
+
def mock_connect(mocker):
|
|
8
|
+
"""Patch trino.dbapi.connect and return the mock connection."""
|
|
9
|
+
mock_conn = MagicMock()
|
|
10
|
+
mocker.patch("pyapiary.dbms_connectors.trino.connect", return_value=mock_conn)
|
|
11
|
+
return mock_conn
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.fixture
|
|
15
|
+
def connector(mock_connect):
|
|
16
|
+
"""Return a TrinoConnector backed by a mocked connection."""
|
|
17
|
+
return TrinoConnector(
|
|
18
|
+
host="trino.trashcollector.dev",
|
|
19
|
+
port=443,
|
|
20
|
+
user="test_user",
|
|
21
|
+
catalog="hive",
|
|
22
|
+
schema="default",
|
|
23
|
+
http_scheme="https",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
# __init__
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
class TestInit:
|
|
32
|
+
def test_connect_called_with_provided_kwargs(self, mocker):
|
|
33
|
+
mock_connect = mocker.patch("pyapiary.dbms_connectors.trino.connect")
|
|
34
|
+
TrinoConnector(host="myhost", port=443, user="alice", http_scheme="https")
|
|
35
|
+
mock_connect.assert_called_once_with(
|
|
36
|
+
host="myhost",
|
|
37
|
+
port=443,
|
|
38
|
+
user="alice",
|
|
39
|
+
http_scheme="https",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
def test_connect_called_with_minimal_kwargs(self, mocker):
|
|
43
|
+
mock_connect = mocker.patch("pyapiary.dbms_connectors.trino.connect")
|
|
44
|
+
TrinoConnector(host="myhost", port=8080, user="alice")
|
|
45
|
+
mock_connect.assert_called_once_with(host="myhost", port=8080, user="alice")
|
|
46
|
+
|
|
47
|
+
def test_conn_attribute_set(self, mock_connect, connector):
|
|
48
|
+
assert connector.conn is mock_connect
|
|
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
|
+
|
|
59
|
+
|
|
60
|
+
# ---------------------------------------------------------------------------
|
|
61
|
+
# query()
|
|
62
|
+
# ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
class TestQuery:
|
|
65
|
+
def test_returns_rows_when_description_present(self, mock_connect, connector):
|
|
66
|
+
mock_cursor = MagicMock()
|
|
67
|
+
mock_cursor.description = [("col1",), ("col2",)]
|
|
68
|
+
mock_cursor.fetchall.return_value = [(1, "a"), (2, "b")]
|
|
69
|
+
mock_connect.cursor.return_value.__enter__.return_value = mock_cursor
|
|
70
|
+
|
|
71
|
+
result = connector.query("SELECT * FROM foo")
|
|
72
|
+
|
|
73
|
+
mock_cursor.execute.assert_called_once_with("SELECT * FROM foo")
|
|
74
|
+
mock_cursor.fetchall.assert_called_once()
|
|
75
|
+
assert result == [(1, "a"), (2, "b")]
|
|
76
|
+
|
|
77
|
+
def test_returns_none_when_no_description(self, mock_connect, connector):
|
|
78
|
+
mock_cursor = MagicMock()
|
|
79
|
+
mock_cursor.description = None
|
|
80
|
+
mock_connect.cursor.return_value.__enter__.return_value = mock_cursor
|
|
81
|
+
|
|
82
|
+
result = connector.query("CREATE TABLE foo (id INT)")
|
|
83
|
+
|
|
84
|
+
mock_cursor.execute.assert_called_once_with("CREATE TABLE foo (id INT)")
|
|
85
|
+
mock_cursor.fetchall.assert_not_called()
|
|
86
|
+
assert result is None
|
|
87
|
+
|
|
88
|
+
def test_returns_empty_list_for_empty_result_set(self, mock_connect, connector):
|
|
89
|
+
mock_cursor = MagicMock()
|
|
90
|
+
mock_cursor.description = [("id",)]
|
|
91
|
+
mock_cursor.fetchall.return_value = []
|
|
92
|
+
mock_connect.cursor.return_value.__enter__.return_value = mock_cursor
|
|
93
|
+
|
|
94
|
+
result = connector.query("SELECT * FROM empty_table")
|
|
95
|
+
|
|
96
|
+
assert result == []
|
|
97
|
+
|
|
98
|
+
def test_cursor_used_as_context_manager(self, mock_connect, connector):
|
|
99
|
+
mock_cursor = MagicMock()
|
|
100
|
+
mock_cursor.description = None
|
|
101
|
+
mock_connect.cursor.return_value.__enter__.return_value = mock_cursor
|
|
102
|
+
|
|
103
|
+
connector.query("SELECT 1")
|
|
104
|
+
|
|
105
|
+
mock_connect.cursor.return_value.__enter__.assert_called_once()
|
|
106
|
+
mock_connect.cursor.return_value.__exit__.assert_called_once()
|
|
107
|
+
|
|
108
|
+
def test_propagates_execute_exception(self, mock_connect, connector):
|
|
109
|
+
mock_cursor = MagicMock()
|
|
110
|
+
mock_cursor.execute.side_effect = RuntimeError("syntax error")
|
|
111
|
+
mock_connect.cursor.return_value.__enter__.return_value = mock_cursor
|
|
112
|
+
|
|
113
|
+
with pytest.raises(RuntimeError, match="syntax error"):
|
|
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",)]
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# ---------------------------------------------------------------------------
|
|
128
|
+
# bulk_insert()
|
|
129
|
+
# ---------------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
class TestBulkInsert:
|
|
132
|
+
def test_inserts_single_row(self, mock_connect, connector):
|
|
133
|
+
mock_cursor = MagicMock()
|
|
134
|
+
mock_connect.cursor.return_value.__enter__.return_value = mock_cursor
|
|
135
|
+
|
|
136
|
+
result = connector.bulk_insert("my_table", [{"id": 1, "name": "alice"}])
|
|
137
|
+
|
|
138
|
+
expected_query = "INSERT INTO my_table (id, name) VALUES (?, ?)"
|
|
139
|
+
mock_cursor.executemany.assert_called_once_with(expected_query, [(1, "alice")])
|
|
140
|
+
assert result is True
|
|
141
|
+
|
|
142
|
+
def test_inserts_multiple_rows(self, mock_connect, connector):
|
|
143
|
+
mock_cursor = MagicMock()
|
|
144
|
+
mock_connect.cursor.return_value.__enter__.return_value = mock_cursor
|
|
145
|
+
|
|
146
|
+
data = [{"id": 1, "val": "a"}, {"id": 2, "val": "b"}, {"id": 3, "val": "c"}]
|
|
147
|
+
result = connector.bulk_insert("my_table", data)
|
|
148
|
+
|
|
149
|
+
actual_values = mock_cursor.executemany.call_args[0][1]
|
|
150
|
+
assert actual_values == [(1, "a"), (2, "b"), (3, "c")]
|
|
151
|
+
assert result is True
|
|
152
|
+
|
|
153
|
+
def test_returns_none_for_empty_list(self, mock_connect, connector, capsys):
|
|
154
|
+
result = connector.bulk_insert("my_table", [])
|
|
155
|
+
|
|
156
|
+
assert result is None
|
|
157
|
+
captured = capsys.readouterr()
|
|
158
|
+
assert "Invalid" in captured.out
|
|
159
|
+
|
|
160
|
+
def test_empty_list_does_not_touch_cursor(self, mock_connect, connector):
|
|
161
|
+
mock_cursor = MagicMock()
|
|
162
|
+
mock_connect.cursor.return_value.__enter__.return_value = mock_cursor
|
|
163
|
+
|
|
164
|
+
connector.bulk_insert("my_table", [])
|
|
165
|
+
|
|
166
|
+
mock_cursor.executemany.assert_not_called()
|
|
167
|
+
|
|
168
|
+
def test_query_string_uses_correct_table_name(self, mock_connect, connector):
|
|
169
|
+
mock_cursor = MagicMock()
|
|
170
|
+
mock_connect.cursor.return_value.__enter__.return_value = mock_cursor
|
|
171
|
+
|
|
172
|
+
connector.bulk_insert("hive.default.target_table", [{"x": 99}])
|
|
173
|
+
|
|
174
|
+
actual_query = mock_cursor.executemany.call_args[0][0]
|
|
175
|
+
assert "hive.default.target_table" in actual_query
|
|
176
|
+
|
|
177
|
+
def test_column_order_matches_first_row_keys(self, mock_connect, connector):
|
|
178
|
+
mock_cursor = MagicMock()
|
|
179
|
+
mock_connect.cursor.return_value.__enter__.return_value = mock_cursor
|
|
180
|
+
|
|
181
|
+
data = [{"z": 3, "a": 1, "m": 2}]
|
|
182
|
+
connector.bulk_insert("t", data)
|
|
183
|
+
|
|
184
|
+
actual_query = mock_cursor.executemany.call_args[0][0]
|
|
185
|
+
for col in ["z", "a", "m"]:
|
|
186
|
+
assert col in actual_query
|
|
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
|
+
|
|
198
|
+
def test_propagates_executemany_exception(self, mock_connect, connector):
|
|
199
|
+
mock_cursor = MagicMock()
|
|
200
|
+
mock_cursor.executemany.side_effect = RuntimeError("DB write error")
|
|
201
|
+
mock_connect.cursor.return_value.__enter__.return_value = mock_cursor
|
|
202
|
+
|
|
203
|
+
with pytest.raises(RuntimeError, match="DB write error"):
|
|
204
|
+
connector.bulk_insert("t", [{"id": 1}])
|
|
205
|
+
|
|
206
|
+
def test_cursor_used_as_context_manager(self, mock_connect, connector):
|
|
207
|
+
mock_cursor = MagicMock()
|
|
208
|
+
mock_connect.cursor.return_value.__enter__.return_value = mock_cursor
|
|
209
|
+
|
|
210
|
+
connector.bulk_insert("t", [{"id": 1}])
|
|
211
|
+
|
|
212
|
+
mock_connect.cursor.return_value.__enter__.assert_called_once()
|
|
213
|
+
mock_connect.cursor.return_value.__exit__.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
|
|
File without changes
|
|
File without changes
|
{pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_domaintools/test_unit_async_domaintools.py
RENAMED
|
File without changes
|
{pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_domaintools/test_unit_domaintools.py
RENAMED
|
File without changes
|
{pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_elasticsearch/test_unit_elasticsearch.py
RENAMED
|
File without changes
|
|
File without changes
|
{pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_flashpoint/test_integration_flashpoint.py
RENAMED
|
File without changes
|
{pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_flashpoint/test_unit_async_flashpoint.py
RENAMED
|
File without changes
|
{pyapiary-2.2.1 → pyapiary-2.3.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.1 → pyapiary-2.3.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
|
|
File without changes
|
{pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_spycloud/test_integration_spycloud.py
RENAMED
|
File without changes
|
{pyapiary-2.2.1 → pyapiary-2.3.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.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_urlscan/test_integration_urlscan.py
RENAMED
|
File without changes
|
{pyapiary-2.2.1 → pyapiary-2.3.1}/src/pyapiary/tests/test_urlscan/test_unit_async_urlscan.py
RENAMED
|
File without changes
|
|
File without changes
|